PlayerComponent.cpp 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976
  1. #include "PlayerComponent.h"
  2. #include <QString>
  3. #include <Qt>
  4. #include <QDir>
  5. #include <QCoreApplication>
  6. #include "display/DisplayComponent.h"
  7. #include "settings/SettingsComponent.h"
  8. #include "system/SystemComponent.h"
  9. #include "utils/Utils.h"
  10. #include "ComponentManager.h"
  11. #include "settings/SettingsSection.h"
  12. #include "PlayerQuickItem.h"
  13. #include "input/InputComponent.h"
  14. #include "QsLog.h"
  15. #include <math.h>
  16. #include <string.h>
  17. #include <shared/Paths.h>
  18. #ifdef TARGET_RPI
  19. #include <bcm_host.h>
  20. #include <interface/vmcs_host/vcgencmd.h>
  21. #endif
  22. ///////////////////////////////////////////////////////////////////////////////////////////////////
  23. static void wakeup_cb(void *context)
  24. {
  25. PlayerComponent *player = (PlayerComponent *)context;
  26. emit player->onMpvEvents();
  27. }
  28. ///////////////////////////////////////////////////////////////////////////////////////////////////
  29. PlayerComponent::PlayerComponent(QObject* parent)
  30. : ComponentBase(parent), m_lastPositionUpdate(0.0), m_playbackAudioDelay(0), m_playbackStartSent(false), m_window(nullptr), m_mediaFrameRate(0),
  31. m_restoreDisplayTimer(this), m_reloadAudioTimer(this),
  32. m_streamSwitchImminent(false)
  33. {
  34. qmlRegisterType<PlayerQuickItem>("Konvergo", 1, 0, "MpvVideo"); // deprecated name
  35. qmlRegisterType<PlayerQuickItem>("Konvergo", 1, 0, "KonvergoVideo");
  36. m_restoreDisplayTimer.setSingleShot(true);
  37. connect(&m_restoreDisplayTimer, &QTimer::timeout, this, &PlayerComponent::onRestoreDisplay);
  38. connect(&DisplayComponent::Get(), &DisplayComponent::refreshRateChanged, this, &PlayerComponent::onRefreshRateChange);
  39. m_reloadAudioTimer.setSingleShot(true);
  40. connect(&m_reloadAudioTimer, &QTimer::timeout, this, &PlayerComponent::onReloadAudio);
  41. }
  42. /////////////////////////////////////////////////////////////////////////////////////////
  43. void PlayerComponent::componentPostInitialize()
  44. {
  45. InputComponent::Get().registerHostCommand("player", this, "userCommand");
  46. }
  47. ///////////////////////////////////////////////////////////////////////////////////////////////////
  48. PlayerComponent::~PlayerComponent()
  49. {
  50. if (m_mpv)
  51. mpv_set_wakeup_callback(m_mpv, nullptr, nullptr);
  52. }
  53. ///////////////////////////////////////////////////////////////////////////////////////////////////
  54. bool PlayerComponent::componentInitialize()
  55. {
  56. m_mpv = mpv::qt::Handle::FromRawHandle(mpv_create());
  57. if (!m_mpv)
  58. throw FatalException(tr("Failed to load mpv."));
  59. mpv_request_log_messages(m_mpv, "terminal-default");
  60. mpv_set_option_string(m_mpv, "msg-level", "all=v");
  61. // No mouse events
  62. mpv_set_option_string(m_mpv, "input-cursor", "no");
  63. mpv_set_option_string(m_mpv, "cursor-autohide", "no");
  64. mpv_set_option_string(m_mpv, "config", "yes");
  65. mpv_set_option_string(m_mpv, "config-dir", Paths::dataDir().toUtf8().data());
  66. // Disable native OSD if mpv_command_string() is used.
  67. mpv_set_option_string(m_mpv, "osd-level", "0");
  68. // This forces the player not to rebase playback time to 0 with mkv. We
  69. // require this, because mkv transcoding lets files start at times other
  70. // than 0, and web-client expects that we return these times unchanged.
  71. mpv::qt::set_option_variant(m_mpv, "demuxer-mkv-probe-start-time", false);
  72. // Always use the internal mixer by default.
  73. mpv_set_option_string(m_mpv, "softvol", "yes");
  74. // Just discard audio output if no audio device could be opened. This gives
  75. // us better flexibility how to react to such errors (instead of just
  76. // aborting playback immediately).
  77. mpv_set_option_string(m_mpv, "audio-fallback-to-null", "yes");
  78. // Do not let the decoder downmix (better customization for us).
  79. mpv::qt::set_option_variant(m_mpv, "ad-lavc-downmix", false);
  80. // Make it load the hwdec interop, so hwdec can be enabled at runtime.
  81. mpv::qt::set_option_variant(m_mpv, "hwdec-preload", "auto");
  82. // User-visible application name used by some audio APIs (at least PulseAudio).
  83. mpv_set_option_string(m_mpv, "audio-client-name", QCoreApplication::applicationName().toUtf8().data());
  84. // User-visible stream title used by some audio APIs (at least PulseAudio and wasapi).
  85. mpv_set_option_string(m_mpv, "title", QCoreApplication::applicationName().toUtf8().data());
  86. // Apply some low-memory settings on RPI, which is relatively memory-constrained.
  87. #ifdef TARGET_RPI
  88. // The backbuffer makes seeking back faster (without having to do a HTTP-level seek)
  89. mpv::qt::set_option_variant(m_mpv, "cache-backbuffer", 10 * 1024); // KB
  90. // The demuxer queue is used for the readahead, and also for dealing with badly
  91. // interlaved audio/video. Setting it too low increases sensitivity to network
  92. // issues, and could cause playback failure with "bad" files.
  93. mpv::qt::set_option_variant(m_mpv, "demuxer-max-bytes", 50 * 1024 * 1024); // bytes
  94. // Specifically for enabling mpeg4.
  95. mpv::qt::set_option_variant(m_mpv, "hwdec-codecs", "all");
  96. // Do not use exact seeks by default. (This affects the start position in the "loadfile"
  97. // command in particular. We override the seek mode for normal "seek" commands.)
  98. mpv::qt::set_option_variant(m_mpv, "hr-seek", "no");
  99. #endif
  100. mpv_observe_property(m_mpv, 0, "pause", MPV_FORMAT_FLAG);
  101. mpv_observe_property(m_mpv, 0, "core-idle", MPV_FORMAT_FLAG);
  102. mpv_observe_property(m_mpv, 0, "cache-buffering-state", MPV_FORMAT_INT64);
  103. mpv_observe_property(m_mpv, 0, "playback-time", MPV_FORMAT_DOUBLE);
  104. mpv_observe_property(m_mpv, 0, "vo-configured", MPV_FORMAT_FLAG);
  105. mpv_observe_property(m_mpv, 0, "duration", MPV_FORMAT_DOUBLE);
  106. mpv_observe_property(m_mpv, 0, "audio-device-list", MPV_FORMAT_NODE);
  107. connect(this, &PlayerComponent::onMpvEvents, this, &PlayerComponent::handleMpvEvents, Qt::QueuedConnection);
  108. mpv_set_wakeup_callback(m_mpv, wakeup_cb, this);
  109. if (mpv_initialize(m_mpv) < 0)
  110. throw FatalException(tr("Failed to initialize mpv."));
  111. // Setup a hook with the ID 1, which is run during the file is loaded.
  112. // Used to delay playback start for display framerate switching.
  113. // (See handler in handleMpvEvent() for details.)
  114. mpv::qt::command_variant(m_mpv, QStringList() << "hook-add" << "on_load" << "1" << "0");
  115. updateAudioDeviceList();
  116. setAudioConfiguration();
  117. updateSubtitleSettings();
  118. updateVideoSettings();
  119. connect(SettingsComponent::Get().getSection(SETTINGS_SECTION_VIDEO), &SettingsSection::valuesUpdated,
  120. this, &PlayerComponent::updateVideoSettings);
  121. connect(SettingsComponent::Get().getSection(SETTINGS_SECTION_SUBTITLES), &SettingsSection::valuesUpdated,
  122. this, &PlayerComponent::updateSubtitleSettings);
  123. connect(SettingsComponent::Get().getSection(SETTINGS_SECTION_AUDIO), &SettingsSection::valuesUpdated,
  124. this, &PlayerComponent::setAudioConfiguration);
  125. initializeCodecSupport();
  126. return true;
  127. }
  128. ///////////////////////////////////////////////////////////////////////////////////////////////////
  129. void PlayerComponent::setQtQuickWindow(QQuickWindow* window)
  130. {
  131. PlayerQuickItem* video = window->findChild<PlayerQuickItem*>("video");
  132. if (!video)
  133. throw FatalException(tr("Failed to load video element."));
  134. mpv_set_option_string(m_mpv, "vo", "opengl-cb");
  135. video->initMpv(this);
  136. }
  137. ///////////////////////////////////////////////////////////////////////////////////////////////////
  138. void PlayerComponent::setRpiWindow(QQuickWindow* window)
  139. {
  140. window->setFlags(Qt::FramelessWindowHint);
  141. mpv_set_option_string(m_mpv, "vo", "rpi");
  142. }
  143. ///////////////////////////////////////////////////////////////////////////////////////////////////
  144. void PlayerComponent::setWindow(QQuickWindow* window)
  145. {
  146. bool useRpi = false;
  147. #ifdef TARGET_RPI
  148. useRpi = true;
  149. #endif
  150. m_window = window;
  151. if (!window)
  152. return;
  153. QString forceVo = SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, "debug.force_vo").toString();
  154. if (forceVo.size())
  155. mpv::qt::set_option_variant(m_mpv, "vo", forceVo);
  156. else if (useRpi)
  157. setRpiWindow(window);
  158. else
  159. setQtQuickWindow(window);
  160. }
  161. ///////////////////////////////////////////////////////////////////////////////////////////////////
  162. bool PlayerComponent::load(const QString& url, const QVariantMap& options, const QVariantMap &metadata, const QString& audioStream , const QString& subtitleStream)
  163. {
  164. stop();
  165. queueMedia(url, options, metadata, audioStream, subtitleStream);
  166. return true;
  167. }
  168. ///////////////////////////////////////////////////////////////////////////////////////////////////
  169. void PlayerComponent::queueMedia(const QString& url, const QVariantMap& options, const QVariantMap &metadata, const QString& audioStream, const QString& subtitleStream)
  170. {
  171. m_mediaFrameRate = metadata["frameRate"].toFloat(); // returns 0 on failure
  172. updateVideoSettings();
  173. QVariantList command;
  174. command << "loadfile" << url;
  175. command << "append-play"; // if nothing is playing, play it now, otherwise just enqueue it
  176. QVariantMap extraArgs;
  177. quint64 startMilliseconds = options["startMilliseconds"].toLongLong();
  178. if (startMilliseconds != 0)
  179. extraArgs.insert("start", "+" + QString::number(startMilliseconds / 1000.0));
  180. // force default audio selection
  181. extraArgs.insert("ff-aid", "auto");
  182. // by default ignore all subtitles, unless overridden
  183. extraArgs.insert("sid", "no");
  184. extraArgs.insert("ff-sid", "auto");
  185. // detect subtitles
  186. if (!subtitleStream.isEmpty())
  187. {
  188. // If the stream title starts with a #, then it's an index
  189. if (subtitleStream.startsWith("#"))
  190. extraArgs.insert("ff-sid", subtitleStream.mid(1));
  191. else {
  192. extraArgs.insert("sub-file", subtitleStream);
  193. extraArgs.insert("sid", "auto"); // select the external one by default
  194. }
  195. }
  196. if (metadata["type"] == "music")
  197. extraArgs.insert("vid", "no");
  198. // and then the audio stream
  199. if (!audioStream.isEmpty())
  200. extraArgs.insert("ff-aid", audioStream);
  201. extraArgs.insert("pause", options["autoplay"].toBool() ? "no" : "yes");
  202. QString userAgent = metadata["headers"].toMap()["User-Agent"].toString();
  203. if (userAgent.size())
  204. extraArgs.insert("user-agent", userAgent);
  205. command << extraArgs;
  206. QLOG_DEBUG() << command;
  207. mpv::qt::command_variant(m_mpv, command);
  208. }
  209. /////////////////////////////////////////////////////////////////////////////////////////
  210. void PlayerComponent::streamSwitch()
  211. {
  212. m_streamSwitchImminent = true;
  213. }
  214. ///////////////////////////////////////////////////////////////////////////////////////////////////
  215. bool PlayerComponent::switchDisplayFrameRate()
  216. {
  217. QLOG_DEBUG() << "Video framerate:" << m_mediaFrameRate << "fps";
  218. if (!SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, "refreshrate.auto_switch").toBool())
  219. {
  220. QLOG_DEBUG() << "Not switching refresh-rate (disabled by settings).";
  221. return false;
  222. }
  223. bool fs = SettingsComponent::Get().value(SETTINGS_SECTION_MAIN, "fullscreen").toBool();
  224. #if KONVERGO_OPENELEC
  225. fs = true;
  226. #endif
  227. if (!fs)
  228. {
  229. QLOG_DEBUG() << "Not switching refresh-rate (not in fullscreen mode).";
  230. return false;
  231. }
  232. if (m_mediaFrameRate < 1)
  233. {
  234. QLOG_DEBUG() << "Not switching refresh-rate (no known video framerate).";
  235. return false;
  236. }
  237. // Make sure a timer started by the previous file ending isn't accidentally
  238. // still in-flight. It could switch the display back after we've switched.
  239. m_restoreDisplayTimer.stop();
  240. DisplayComponent* display = &DisplayComponent::Get();
  241. if (!display->switchToBestVideoMode(m_mediaFrameRate))
  242. {
  243. QLOG_DEBUG() << "Switching refresh-rate failed or unnecessary.";
  244. return false;
  245. }
  246. // Make sure settings dependent on the display refresh rate are updated properly.
  247. updateVideoSettings();
  248. return true;
  249. }
  250. ///////////////////////////////////////////////////////////////////////////////////////////////////
  251. void PlayerComponent::onRestoreDisplay()
  252. {
  253. // If the player will in fact start another file (or is playing one), don't restore.
  254. if (mpv::qt::get_property_variant(m_mpv, "idle").toBool())
  255. DisplayComponent::Get().restorePreviousVideoMode();
  256. }
  257. ///////////////////////////////////////////////////////////////////////////////////////////////////
  258. void PlayerComponent::onRefreshRateChange()
  259. {
  260. // Make sure settings dependent on the display refresh rate are updated properly.
  261. updateVideoSettings();
  262. }
  263. ///////////////////////////////////////////////////////////////////////////////////////////////////
  264. void PlayerComponent::handleMpvEvent(mpv_event *event)
  265. {
  266. switch (event->event_id)
  267. {
  268. case MPV_EVENT_START_FILE:
  269. {
  270. m_currentUrl = mpv::qt::get_property_variant(m_mpv, "path").toString();
  271. m_playbackStartSent = false;
  272. break;
  273. }
  274. case MPV_EVENT_FILE_LOADED:
  275. {
  276. emit playing(m_currentUrl);
  277. break;
  278. }
  279. case MPV_EVENT_END_FILE:
  280. {
  281. mpv_event_end_file *endFile = (mpv_event_end_file *)event->data;
  282. switch (endFile->reason)
  283. {
  284. case MPV_END_FILE_REASON_EOF:
  285. emit finished(m_currentUrl);
  286. break;
  287. case MPV_END_FILE_REASON_ERROR:
  288. emit error(endFile->error, mpv_error_string(endFile->error));
  289. break;
  290. default:
  291. emit stopped(m_currentUrl);
  292. break;
  293. }
  294. emit playbackEnded(m_currentUrl);
  295. m_currentUrl = "";
  296. if (!m_streamSwitchImminent)
  297. m_restoreDisplayTimer.start(0);
  298. m_streamSwitchImminent = false;
  299. break;
  300. }
  301. case MPV_EVENT_IDLE:
  302. {
  303. emit playbackAllDone();
  304. break;
  305. }
  306. case MPV_EVENT_PLAYBACK_RESTART:
  307. {
  308. // it's also sent after seeks are completed
  309. if (!m_playbackStartSent)
  310. emit playbackStarting();
  311. m_playbackStartSent = true;
  312. break;
  313. }
  314. case MPV_EVENT_PROPERTY_CHANGE:
  315. {
  316. mpv_event_property *prop = (mpv_event_property *)event->data;
  317. if (strcmp(prop->name, "pause") == 0 && prop->format == MPV_FORMAT_FLAG)
  318. {
  319. int state = *(int *)prop->data;
  320. emit paused(state);
  321. }
  322. else if (strcmp(prop->name, "core-idle") == 0 && prop->format == MPV_FORMAT_FLAG)
  323. {
  324. emit playbackActive(!*(int *)prop->data);
  325. }
  326. else if (strcmp(prop->name, "cache-buffering-state") == 0 && prop->format == MPV_FORMAT_INT64)
  327. {
  328. int64_t percentage = *(int64_t *)prop->data;
  329. emit buffering(percentage);
  330. }
  331. else if (strcmp(prop->name, "playback-time") == 0 && prop->format == MPV_FORMAT_DOUBLE)
  332. {
  333. double pos = *(double*)prop->data;
  334. if (fabs(pos - m_lastPositionUpdate) > 0.015)
  335. {
  336. quint64 ms = (quint64)(qMax(pos * 1000.0, 0.0));
  337. emit positionUpdate(ms);
  338. m_lastPositionUpdate = pos;
  339. }
  340. }
  341. else if (strcmp(prop->name, "vo-configured") == 0)
  342. {
  343. int state = prop->format == MPV_FORMAT_FLAG ? *(int *)prop->data : 0;
  344. emit windowVisible(state);
  345. }
  346. else if (strcmp(prop->name, "duration") == 0)
  347. {
  348. if (prop->format == MPV_FORMAT_DOUBLE)
  349. emit updateDuration(*(double *)prop->data * 1000.0);
  350. }
  351. else if (strcmp(prop->name, "audio-device-list") == 0)
  352. {
  353. updateAudioDeviceList();
  354. }
  355. break;
  356. }
  357. case MPV_EVENT_LOG_MESSAGE:
  358. {
  359. mpv_event_log_message *msg = (mpv_event_log_message *)event->data;
  360. // Strip the trailing '\n'
  361. size_t len = strlen(msg->text);
  362. if (len > 0 && msg->text[len - 1] == '\n')
  363. len -= 1;
  364. QString logline = QString::fromUtf8(msg->prefix) + ": " + QString::fromUtf8(msg->text, len);
  365. if (msg->log_level >= MPV_LOG_LEVEL_V)
  366. QLOG_DEBUG() << qPrintable(logline);
  367. else if (msg->log_level >= MPV_LOG_LEVEL_INFO)
  368. QLOG_INFO() << qPrintable(logline);
  369. else if (msg->log_level >= MPV_LOG_LEVEL_WARN)
  370. QLOG_WARN() << qPrintable(logline);
  371. else
  372. QLOG_ERROR() << qPrintable(logline);
  373. break;
  374. }
  375. case MPV_EVENT_CLIENT_MESSAGE:
  376. {
  377. mpv_event_client_message *msg = (mpv_event_client_message *)event->data;
  378. // This happens when the player is about to load the file, but no actual loading has taken part yet.
  379. // We use this to block loading until we explicitly tell it to continue.
  380. if (msg->num_args >= 3 && !strcmp(msg->args[0], "hook_run") && !strcmp(msg->args[1], "1"))
  381. {
  382. QString resumeId = QString::fromUtf8(msg->args[2]);
  383. // Calling this lambda will instruct mpv to continue loading the file.
  384. auto resume = [=] {
  385. QLOG_INFO() << "resuming loading";
  386. mpv::qt::command_variant(m_mpv, QStringList() << "hook-ack" << resumeId);
  387. };
  388. if (switchDisplayFrameRate())
  389. {
  390. // Now wait for some time for mode change - this is needed because mode changing can take some
  391. // time, during which the screen is black, and initializing hardware decoding could fail due
  392. // to various strange OS-related reasons.
  393. // (Better hope the user doesn't try to exit Konvergo during mode change.)
  394. int pause = SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, "refreshrate.delay").toInt() * 1000;
  395. QLOG_INFO() << "waiting" << pause << "msec after rate switch before loading";
  396. QTimer::singleShot(pause, resume);
  397. }
  398. else
  399. {
  400. resume();
  401. }
  402. break;
  403. }
  404. }
  405. default:; /* ignore */
  406. }
  407. }
  408. ///////////////////////////////////////////////////////////////////////////////////////////////////
  409. void PlayerComponent::handleMpvEvents()
  410. {
  411. // Process all events, until the event queue is empty.
  412. while (1)
  413. {
  414. mpv_event *event = mpv_wait_event(m_mpv, 0);
  415. if (event->event_id == MPV_EVENT_NONE)
  416. break;
  417. handleMpvEvent(event);
  418. }
  419. }
  420. ///////////////////////////////////////////////////////////////////////////////////////////////////
  421. void PlayerComponent::setVideoOnlyMode(bool enable)
  422. {
  423. if (m_window)
  424. {
  425. QQuickItem *web = m_window->findChild<QQuickItem *>("web");
  426. if (web)
  427. web->setVisible(!enable);
  428. }
  429. }
  430. ///////////////////////////////////////////////////////////////////////////////////////////////////
  431. void PlayerComponent::play()
  432. {
  433. QStringList args = (QStringList() << "set" << "pause" << "no");
  434. mpv::qt::command_variant(m_mpv, args);
  435. }
  436. ///////////////////////////////////////////////////////////////////////////////////////////////////
  437. void PlayerComponent::stop()
  438. {
  439. QStringList args("stop");
  440. mpv::qt::command_variant(m_mpv, args);
  441. }
  442. ///////////////////////////////////////////////////////////////////////////////////////////////////
  443. void PlayerComponent::clearQueue()
  444. {
  445. QStringList args("playlist_clear");
  446. mpv::qt::command_variant(m_mpv, args);
  447. }
  448. ///////////////////////////////////////////////////////////////////////////////////////////////////
  449. void PlayerComponent::pause()
  450. {
  451. QStringList args = (QStringList() << "set" << "pause" << "yes");
  452. mpv::qt::command_variant(m_mpv, args);
  453. }
  454. ///////////////////////////////////////////////////////////////////////////////////////////////////
  455. void PlayerComponent::seekTo(qint64 ms)
  456. {
  457. double timeSecs = ms / 1000.0;
  458. QVariantList args = (QVariantList() << "seek" << timeSecs << "absolute+exact");
  459. mpv::qt::command_variant(m_mpv, args);
  460. }
  461. ///////////////////////////////////////////////////////////////////////////////////////////////////
  462. QVariant PlayerComponent::getAudioDeviceList()
  463. {
  464. return mpv::qt::get_property_variant(m_mpv, "audio-device-list");
  465. }
  466. ///////////////////////////////////////////////////////////////////////////////////////////////////
  467. void PlayerComponent::setAudioDevice(const QString& name)
  468. {
  469. mpv::qt::set_property_variant(m_mpv, "audio-device", name);
  470. }
  471. ///////////////////////////////////////////////////////////////////////////////////////////////////
  472. void PlayerComponent::setVolume(int volume)
  473. {
  474. // Will fail if no audio output opened (i.e. no file playing)
  475. mpv::qt::set_property_variant(m_mpv, "volume", volume);
  476. }
  477. ///////////////////////////////////////////////////////////////////////////////////////////////////
  478. int PlayerComponent::volume()
  479. {
  480. QVariant volume = mpv::qt::get_property_variant(m_mpv, "volume");
  481. if (volume.isValid())
  482. return volume.toInt();
  483. return 0;
  484. }
  485. ///////////////////////////////////////////////////////////////////////////////////////////////////
  486. void PlayerComponent::setMuted(bool muted)
  487. {
  488. // Will fail if no audio output opened (i.e. no file playing)
  489. mpv::qt::set_property_variant(m_mpv, "mute", muted);
  490. }
  491. ///////////////////////////////////////////////////////////////////////////////////////////////////
  492. bool PlayerComponent::muted()
  493. {
  494. QVariant mute = mpv::qt::get_property_variant(m_mpv, "mute");
  495. if (mute.isValid())
  496. return mute.toBool();
  497. return false;
  498. }
  499. ///////////////////////////////////////////////////////////////////////////////////////////////////
  500. void PlayerComponent::setSubtitleStream(const QString &subtitleStream)
  501. {
  502. if (subtitleStream.isEmpty())
  503. {
  504. mpv::qt::set_property_variant(m_mpv, "ff-sid", "no");
  505. }
  506. else if (subtitleStream.startsWith("#"))
  507. {
  508. mpv::qt::set_property_variant(m_mpv, "ff-sid", subtitleStream.mid(1));
  509. }
  510. else
  511. {
  512. QStringList args = (QStringList() << "sub-add" << subtitleStream << "cached");
  513. mpv::qt::command_variant(m_mpv, args);
  514. }
  515. }
  516. ///////////////////////////////////////////////////////////////////////////////////////////////////
  517. void PlayerComponent::setAudioStream(const QString &audioStream)
  518. {
  519. if (audioStream.isEmpty())
  520. mpv::qt::set_property_variant(m_mpv, "ff-aid", "no");
  521. else
  522. mpv::qt::set_property_variant(m_mpv, "ff-aid", audioStream);
  523. }
  524. /////////////////////////////////////////////////////////////////////////////////////////
  525. void PlayerComponent::setAudioDelay(qint64 milliseconds)
  526. {
  527. m_playbackAudioDelay = milliseconds;
  528. double displayFps = DisplayComponent::Get().currentRefreshRate();
  529. const char *audioDelaySetting = "audio_delay.normal";
  530. if (fabs(displayFps - 24) < 1) // cover 24Hz, 23.976Hz, and values very close
  531. audioDelaySetting = "audio_delay.24hz";
  532. double fixedDelay = SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, audioDelaySetting).toFloat();
  533. mpv::qt::set_option_variant(m_mpv, "audio-delay", (fixedDelay + m_playbackAudioDelay) / 1000.0);
  534. }
  535. /////////////////////////////////////////////////////////////////////////////////////////
  536. void PlayerComponent::setSubtitleDelay(qint64 milliseconds)
  537. {
  538. mpv::qt::set_option_variant(m_mpv, "sub-delay", milliseconds / 1000.0);
  539. }
  540. ///////////////////////////////////////////////////////////////////////////////////////////////////
  541. void PlayerComponent::onReloadAudio()
  542. {
  543. mpv::qt::command_variant(m_mpv, QStringList() << "ao-reload");
  544. }
  545. ///////////////////////////////////////////////////////////////////////////////////////////////////
  546. // This is called with the set of previous audio devices that were detected, and the set of current
  547. // audio devices. From this we guess whether we should reopen the audio device. If the user-selected
  548. // device went away previously, and now comes back, reinitializing the player's audio output will
  549. // force the player and/or the OS to move audio output back to the user-selected device.
  550. void PlayerComponent::checkCurrentAudioDevice(const QSet<QString>& old_devs, const QSet<QString>& new_devs)
  551. {
  552. QString userDevice = SettingsComponent::Get().value(SETTINGS_SECTION_AUDIO, "device").toString();
  553. QSet<QString> removed = old_devs - new_devs;
  554. QSet<QString> added = new_devs - old_devs;
  555. QLOG_DEBUG() << "Audio devices removed:" << removed;
  556. QLOG_DEBUG() << "Audio devices added:" << added;
  557. QLOG_DEBUG() << "Audio device selected:" << userDevice;
  558. if (!mpv::qt::get_property_variant(m_mpv, "idle").toBool() && userDevice.length())
  559. {
  560. if (added.contains(userDevice))
  561. {
  562. // The timer is for debouncing the reload. Several change notifications could
  563. // come in quick succession. Also, it's possible that trying to open the
  564. // reappeared audio device immediately can fail.
  565. m_reloadAudioTimer.start(500);
  566. }
  567. }
  568. }
  569. ///////////////////////////////////////////////////////////////////////////////////////////////////
  570. void PlayerComponent::updateAudioDeviceList()
  571. {
  572. QVariantList settingList;
  573. QVariant list = getAudioDeviceList();
  574. QSet<QString> devices;
  575. for(const QVariant& d : list.toList())
  576. {
  577. Q_ASSERT(d.type() == QVariant::Map);
  578. QVariantMap dmap = d.toMap();
  579. devices.insert(dmap["name"].toString());
  580. QVariantMap entry;
  581. entry["value"] = dmap["name"];
  582. entry["title"] = dmap["description"];
  583. settingList << entry;
  584. }
  585. SettingsComponent::Get().updatePossibleValues(SETTINGS_SECTION_AUDIO, "device", settingList);
  586. checkCurrentAudioDevice(m_audioDevices, devices);
  587. m_audioDevices = devices;
  588. }
  589. ///////////////////////////////////////////////////////////////////////////////////////////////////
  590. void PlayerComponent::setAudioConfiguration()
  591. {
  592. QStringList aoDefaults;
  593. QString deviceType = SettingsComponent::Get().value(SETTINGS_SECTION_AUDIO, "devicetype").toString();
  594. if (SettingsComponent::Get().value(SETTINGS_SECTION_AUDIO, "exclusive").toBool())
  595. {
  596. aoDefaults << "wasapi:exclusive=yes";
  597. aoDefaults << "coreaudio:exclusive=yes";
  598. }
  599. mpv::qt::set_option_variant(m_mpv, "ao-defaults", aoDefaults.join(','));
  600. // set the audio device
  601. QVariant device = SettingsComponent::Get().value(SETTINGS_SECTION_AUDIO, "device");
  602. mpv::qt::set_property_variant(m_mpv, "audio-device", device);
  603. QString resampleOpts = "";
  604. bool normalize = SettingsComponent::Get().value(SETTINGS_SECTION_AUDIO, "normalize").toBool();
  605. resampleOpts += QString(":normalize=") + (normalize ? "yes" : "no");
  606. // Make downmix more similar to PHT.
  607. resampleOpts += ":o=[surround_mix_level=1]";
  608. mpv::qt::set_option_variant(m_mpv, "af-defaults", "lavrresample" + resampleOpts);
  609. QString passthroughCodecs;
  610. // passthrough doesn't make sense with basic type
  611. if (deviceType != AUDIO_DEVICE_TYPE_BASIC)
  612. {
  613. QStringList enabledCodecs;
  614. SettingsSection* audioSection = SettingsComponent::Get().getSection(SETTINGS_SECTION_AUDIO);
  615. QStringList codecs;
  616. if (deviceType == AUDIO_DEVICE_TYPE_SPDIF)
  617. codecs = AudioCodecsSPDIF();
  618. else if (deviceType == AUDIO_DEVICE_TYPE_HDMI && audioSection->value("advanced").toBool())
  619. codecs = AudioCodecsAll();
  620. for(const QString& key : codecs)
  621. {
  622. if (audioSection->value("passthrough." + key).toBool())
  623. enabledCodecs << key;
  624. }
  625. // dts-hd includes dts, but listing dts before dts-hd may disable dts-hd.
  626. if (enabledCodecs.indexOf("dts-hd") != -1)
  627. enabledCodecs.removeAll("dts");
  628. passthroughCodecs = enabledCodecs.join(",");
  629. }
  630. mpv::qt::set_option_variant(m_mpv, "audio-spdif", passthroughCodecs);
  631. // set the channel layout
  632. QVariant layout = SettingsComponent::Get().value(SETTINGS_SECTION_AUDIO, "channels");
  633. // always force either stereo or transcoding
  634. if (deviceType == AUDIO_DEVICE_TYPE_SPDIF)
  635. layout = "2.0";
  636. mpv::qt::set_option_variant(m_mpv, "audio-channels", layout);
  637. // if the user has indicated that PCM only works for stereo, and that
  638. // the receiver supports AC3, set this extra option that allows us to transcode
  639. // 5.1 audio into a usable format, note that we only support AC3
  640. // here for now. We might need to add support for DTS transcoding
  641. // if we see user requests for it.
  642. //
  643. bool doAc3Transcoding = false;
  644. if (layout == "2.0" &&
  645. SettingsComponent::Get().value(SETTINGS_SECTION_AUDIO, "passthrough.ac3").toBool())
  646. {
  647. mpv::qt::command_variant(m_mpv, QStringList() << "af" << "add" << "@ac3:lavcac3enc");
  648. doAc3Transcoding = true;
  649. }
  650. else
  651. {
  652. mpv::qt::command_variant(m_mpv, QStringList() << "af" << "del" << "@ac3");
  653. }
  654. // Make a informational log message.
  655. QString audioConfig = QString(QString("Audio Config - device: %1, ") +
  656. "channel layout: %2, " +
  657. "passthrough codecs: %3, " +
  658. "ac3 transcoding: %4").arg(device.toString(),
  659. layout.toString(),
  660. passthroughCodecs.isEmpty() ? "none" : passthroughCodecs,
  661. doAc3Transcoding ? "yes" : "no");
  662. QLOG_INFO() << qPrintable(audioConfig);
  663. }
  664. ///////////////////////////////////////////////////////////////////////////////////////////////////
  665. void PlayerComponent::updateSubtitleSettings()
  666. {
  667. QVariant size = SettingsComponent::Get().value(SETTINGS_SECTION_SUBTITLES, "size");
  668. mpv::qt::set_option_variant(m_mpv, "sub-text-font-size", size);
  669. QVariant colorsString = SettingsComponent::Get().value(SETTINGS_SECTION_SUBTITLES, "color");
  670. auto colors = colorsString.toString().split(",");
  671. if (colors.length() == 2)
  672. {
  673. mpv::qt::set_option_variant(m_mpv, "sub-text-color", colors[0]);
  674. mpv::qt::set_option_variant(m_mpv, "sub-text-border-color", colors[1]);
  675. }
  676. QVariant subposString = SettingsComponent::Get().value(SETTINGS_SECTION_SUBTITLES, "placement");
  677. auto subpos = subposString.toString().split(",");
  678. if (subpos.length() == 2)
  679. {
  680. mpv::qt::set_option_variant(m_mpv, "sub-text-align-x", subpos[0]);
  681. mpv::qt::set_option_variant(m_mpv, "sub-text-align-y", subpos[1]);
  682. }
  683. }
  684. ///////////////////////////////////////////////////////////////////////////////////////////////////
  685. void PlayerComponent::updateVideoSettings()
  686. {
  687. QVariant syncMode = SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, "sync_mode");
  688. mpv::qt::set_option_variant(m_mpv, "video-sync", syncMode);
  689. QVariant hardwareDecoding = SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, "hardware_decoding");
  690. mpv::qt::set_property_variant(m_mpv, "hwdec", hardwareDecoding.toBool() ? "auto" : "no");
  691. QVariant deinterlace = SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, "deinterlace");
  692. mpv::qt::set_option_variant(m_mpv, "deinterlace", deinterlace.toBool() ? "yes" : "no");
  693. #ifndef TARGET_RPI
  694. double displayFps = DisplayComponent::Get().currentRefreshRate();
  695. mpv::qt::set_property_variant(m_mpv, "display-fps", displayFps);
  696. #endif
  697. setAudioDelay(m_playbackAudioDelay);
  698. QVariant cache = SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, "cache");
  699. mpv::qt::set_option_variant(m_mpv, "cache", cache.toInt() * 1024);
  700. }
  701. /////////////////////////////////////////////////////////////////////////////////////////
  702. void PlayerComponent::initializeCodecSupport()
  703. {
  704. QMap<QString, QString> all = { {"vc1", "WVC1"}, {"mpeg2video", "MPG2"} };
  705. for (auto name : all.keys())
  706. {
  707. bool ok = true;
  708. #ifdef TARGET_RPI
  709. char res[100] = "";
  710. bcm_host_init();
  711. if (vc_gencmd(res, sizeof(res), "codec_enabled %s", all[name].toUtf8().data()))
  712. res[0] = '\0'; // error
  713. ok = !!strstr(res, "=enabled");
  714. #endif
  715. m_codecSupport[name] = ok;
  716. QLOG_INFO() << "Codec" << name << (ok ? "present" : "disabled");
  717. }
  718. }
  719. /////////////////////////////////////////////////////////////////////////////////////////
  720. bool PlayerComponent::checkCodecSupport(const QString& codec)
  721. {
  722. if (m_codecSupport.contains(codec))
  723. return m_codecSupport[codec];
  724. return true; // doesn't matter if unknown codecs are reported as "ok"
  725. }
  726. /////////////////////////////////////////////////////////////////////////////////////////
  727. void PlayerComponent::userCommand(QString command)
  728. {
  729. QByteArray cmdUtf8 = command.toUtf8();
  730. mpv_command_string(m_mpv, cmdUtf8.data());
  731. }
  732. /////////////////////////////////////////////////////////////////////////////////////////
  733. static QString get_mpv_osd(mpv_handle *ctx, const QString& property)
  734. {
  735. char *s = mpv_get_property_osd_string(ctx, property.toUtf8().data());
  736. if (!s)
  737. return "-";
  738. QString r = QString::fromUtf8(s);
  739. mpv_free(s);
  740. return r;
  741. }
  742. #define MPV_PROPERTY(p) get_mpv_osd(m_mpv, p)
  743. #define MPV_PROPERTY_BOOL(p) (mpv::qt::get_property_variant(m_mpv, p).toBool())
  744. /////////////////////////////////////////////////////////////////////////////////////////
  745. void PlayerComponent::appendAudioFormat(QTextStream& info, const QString& property) const
  746. {
  747. // Guess if it's a passthrough format. Don't show the channel layout in this
  748. // case, because it's confusing.
  749. QString audioFormat = MPV_PROPERTY(property + "/format");
  750. if (audioFormat.startsWith("spdif-"))
  751. {
  752. info << "passthrough (" << audioFormat.mid(6) << ")";
  753. }
  754. else
  755. {
  756. QString hr = MPV_PROPERTY(property + "/hr-channels");
  757. QString full = MPV_PROPERTY(property + "/channels");
  758. info << hr;
  759. if (hr != full)
  760. info << " (" << full << ")";
  761. }
  762. }
  763. /////////////////////////////////////////////////////////////////////////////////////////
  764. QString PlayerComponent::videoInformation() const
  765. {
  766. QString infoStr;
  767. QTextStream info(&infoStr);
  768. // check if video is playing
  769. if (mpv::qt::get_property_variant(m_mpv, "idle").toBool())
  770. return "";
  771. info << "File:" << endl;
  772. info << "URL: " << MPV_PROPERTY("path") << endl;
  773. info << "Container: " << MPV_PROPERTY("file-format") << endl;
  774. info << "Native seeking: " << ((MPV_PROPERTY_BOOL("seekable") &&
  775. !MPV_PROPERTY_BOOL("partially-seekable"))
  776. ? "yes" : "no") << endl;
  777. info << endl;
  778. info << "Video:" << endl;
  779. info << "Codec: " << MPV_PROPERTY("video-codec") << endl;
  780. info << "Size: " << MPV_PROPERTY("video-params/dw") << "x"
  781. << MPV_PROPERTY("video-params/dh") << endl;
  782. info << "FPS (container): " << MPV_PROPERTY("fps") << endl;
  783. info << "FPS (filters): " << MPV_PROPERTY("estimated-vf-fps") << endl;
  784. info << "Aspect: " << MPV_PROPERTY("video-aspect") << endl;
  785. info << "Bitrate: " << MPV_PROPERTY("video-bitrate") << endl;
  786. double displayFps = DisplayComponent::Get().currentRefreshRate();
  787. info << "Display FPS: " << MPV_PROPERTY("display-fps")
  788. << " (" << displayFps << ")" << endl;
  789. info << "Hardware Decoding: " << MPV_PROPERTY("hwdec-active")
  790. << " (" << MPV_PROPERTY("hwdec-detected") << ")" << endl;
  791. info << endl;
  792. info << "Audio: " << endl;
  793. info << "Codec: " << MPV_PROPERTY("audio-codec") << endl;
  794. info << "Bitrate: " << MPV_PROPERTY("audio-bitrate") << endl;
  795. info << "Channels (input): ";
  796. appendAudioFormat(info, "audio-params");
  797. info << endl;
  798. info << "Channels (output): ";
  799. appendAudioFormat(info, "audio-out-params");
  800. info << endl;
  801. info << endl;
  802. info << "Performance: " << endl;
  803. info << "A/V: " << MPV_PROPERTY("avsync") << endl;
  804. info << "Dropped frames: " << MPV_PROPERTY("vo-drop-frame-count") << endl;
  805. bool dispSync = MPV_PROPERTY_BOOL("display-sync-active");
  806. info << "Display Sync: ";
  807. if (!dispSync)
  808. {
  809. info << "no" << endl;
  810. }
  811. else
  812. {
  813. info << "yes (ratio " << MPV_PROPERTY("vsync-ratio") << ")" << endl;
  814. info << "Mistimed frames: " << MPV_PROPERTY("mistimed-frame-count")
  815. << "/" << MPV_PROPERTY("vo-delayed-frame-count") << endl;
  816. info << "Measured FPS: " << MPV_PROPERTY("estimated-display-fps")
  817. << " (" << MPV_PROPERTY("vsync-jitter") << ")" << endl;
  818. info << "V. speed corr.: " << MPV_PROPERTY("video-speed-correction") << endl;
  819. info << "A. speed corr.: " << MPV_PROPERTY("audio-speed-correction") << endl;
  820. }
  821. info << endl;
  822. info << "Cache:" << endl;
  823. info << "Seconds: " << MPV_PROPERTY("demuxer-cache-duration") << endl;
  824. info << "Extra readahead: " << MPV_PROPERTY("cache-used") << endl;
  825. info << "Buffering: " << MPV_PROPERTY("cache-buffering-state") << endl;
  826. info << endl;
  827. info << "Misc: " << endl;
  828. info << "Time: " << MPV_PROPERTY("playback-time") << " / "
  829. << MPV_PROPERTY("duration")
  830. << " (" << MPV_PROPERTY("percent-pos") << "%)" << endl;
  831. info << "State: " << (MPV_PROPERTY_BOOL("pause") ? "paused " : "")
  832. << (MPV_PROPERTY_BOOL("paused-for-cache") ? "buffering " : "")
  833. << (MPV_PROPERTY_BOOL("core-idle") ? "waiting " : "playing ")
  834. << (MPV_PROPERTY_BOOL("seeking") ? "seeking " : "")
  835. << endl;
  836. info << flush;
  837. return infoStr;
  838. }