PlayerComponent.cpp 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119
  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), m_doAc3Transcoding(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. // Setup a hook with the ID 1, which is run at a certain stage during loading.
  116. // We use it to probe the codecs.
  117. mpv::qt::command_variant(m_mpv, QStringList() << "hook-add" << "on_preloaded" << "2" << "0");
  118. updateAudioDeviceList();
  119. setAudioConfiguration();
  120. updateSubtitleSettings();
  121. updateVideoSettings();
  122. connect(SettingsComponent::Get().getSection(SETTINGS_SECTION_VIDEO), &SettingsSection::valuesUpdated,
  123. this, &PlayerComponent::updateVideoSettings);
  124. connect(SettingsComponent::Get().getSection(SETTINGS_SECTION_SUBTITLES), &SettingsSection::valuesUpdated,
  125. this, &PlayerComponent::updateSubtitleSettings);
  126. connect(SettingsComponent::Get().getSection(SETTINGS_SECTION_AUDIO), &SettingsSection::valuesUpdated,
  127. this, &PlayerComponent::setAudioConfiguration);
  128. initializeCodecSupport();
  129. Codecs::updateCachedCodecList();
  130. return true;
  131. }
  132. ///////////////////////////////////////////////////////////////////////////////////////////////////
  133. void PlayerComponent::setQtQuickWindow(QQuickWindow* window)
  134. {
  135. PlayerQuickItem* video = window->findChild<PlayerQuickItem*>("video");
  136. if (!video)
  137. throw FatalException(tr("Failed to load video element."));
  138. mpv_set_option_string(m_mpv, "vo", "opengl-cb");
  139. video->initMpv(this);
  140. }
  141. ///////////////////////////////////////////////////////////////////////////////////////////////////
  142. void PlayerComponent::setRpiWindow(QQuickWindow* window)
  143. {
  144. window->setFlags(Qt::FramelessWindowHint);
  145. mpv_set_option_string(m_mpv, "vo", "rpi");
  146. }
  147. ///////////////////////////////////////////////////////////////////////////////////////////////////
  148. void PlayerComponent::setWindow(QQuickWindow* window)
  149. {
  150. bool useRpi = false;
  151. #ifdef TARGET_RPI
  152. useRpi = true;
  153. #endif
  154. m_window = window;
  155. if (!window)
  156. return;
  157. QString forceVo = SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, "debug.force_vo").toString();
  158. if (forceVo.size())
  159. mpv::qt::set_option_variant(m_mpv, "vo", forceVo);
  160. else if (useRpi)
  161. setRpiWindow(window);
  162. else
  163. setQtQuickWindow(window);
  164. }
  165. ///////////////////////////////////////////////////////////////////////////////////////////////////
  166. bool PlayerComponent::load(const QString& url, const QVariantMap& options, const QVariantMap &metadata, const QString& audioStream , const QString& subtitleStream)
  167. {
  168. stop();
  169. queueMedia(url, options, metadata, audioStream, subtitleStream);
  170. return true;
  171. }
  172. ///////////////////////////////////////////////////////////////////////////////////////////////////
  173. void PlayerComponent::queueMedia(const QString& url, const QVariantMap& options, const QVariantMap &metadata, const QString& audioStream, const QString& subtitleStream)
  174. {
  175. m_mediaFrameRate = metadata["frameRate"].toFloat(); // returns 0 on failure
  176. m_serverMediaInfo = metadata["media"].toMap();
  177. updateVideoSettings();
  178. QVariantList command;
  179. command << "loadfile" << url;
  180. command << "append-play"; // if nothing is playing, play it now, otherwise just enqueue it
  181. QVariantMap extraArgs;
  182. quint64 startMilliseconds = options["startMilliseconds"].toLongLong();
  183. if (startMilliseconds != 0)
  184. extraArgs.insert("start", "+" + QString::number(startMilliseconds / 1000.0));
  185. // force default audio selection
  186. extraArgs.insert("ff-aid", "auto");
  187. // by default ignore all subtitles, unless overridden
  188. extraArgs.insert("sid", "no");
  189. extraArgs.insert("ff-sid", "auto");
  190. // detect subtitles
  191. if (!subtitleStream.isEmpty())
  192. {
  193. // If the stream title starts with a #, then it's an index
  194. if (subtitleStream.startsWith("#"))
  195. extraArgs.insert("ff-sid", subtitleStream.mid(1));
  196. else {
  197. extraArgs.insert("sub-file", subtitleStream);
  198. extraArgs.insert("sid", "auto"); // select the external one by default
  199. }
  200. }
  201. if (metadata["type"] == "music")
  202. extraArgs.insert("vid", "no");
  203. // and then the audio stream
  204. if (!audioStream.isEmpty())
  205. extraArgs.insert("ff-aid", audioStream);
  206. extraArgs.insert("pause", options["autoplay"].toBool() ? "no" : "yes");
  207. QString userAgent = metadata["headers"].toMap()["User-Agent"].toString();
  208. if (userAgent.size())
  209. extraArgs.insert("user-agent", userAgent);
  210. // Make sure the list of requested codecs is reset.
  211. extraArgs.insert("ad", "");
  212. extraArgs.insert("vd", "");
  213. command << extraArgs;
  214. QLOG_DEBUG() << command;
  215. mpv::qt::command_variant(m_mpv, command);
  216. }
  217. /////////////////////////////////////////////////////////////////////////////////////////
  218. void PlayerComponent::streamSwitch()
  219. {
  220. m_streamSwitchImminent = true;
  221. }
  222. ///////////////////////////////////////////////////////////////////////////////////////////////////
  223. bool PlayerComponent::switchDisplayFrameRate()
  224. {
  225. QLOG_DEBUG() << "Video framerate:" << m_mediaFrameRate << "fps";
  226. if (!SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, "refreshrate.auto_switch").toBool())
  227. {
  228. QLOG_DEBUG() << "Not switching refresh-rate (disabled by settings).";
  229. return false;
  230. }
  231. bool fs = SettingsComponent::Get().value(SETTINGS_SECTION_MAIN, "fullscreen").toBool();
  232. #if KONVERGO_OPENELEC
  233. fs = true;
  234. #endif
  235. if (!fs)
  236. {
  237. QLOG_DEBUG() << "Not switching refresh-rate (not in fullscreen mode).";
  238. return false;
  239. }
  240. if (m_mediaFrameRate < 1)
  241. {
  242. QLOG_DEBUG() << "Not switching refresh-rate (no known video framerate).";
  243. return false;
  244. }
  245. // Make sure a timer started by the previous file ending isn't accidentally
  246. // still in-flight. It could switch the display back after we've switched.
  247. m_restoreDisplayTimer.stop();
  248. DisplayComponent* display = &DisplayComponent::Get();
  249. if (!display->switchToBestVideoMode(m_mediaFrameRate))
  250. {
  251. QLOG_DEBUG() << "Switching refresh-rate failed or unnecessary.";
  252. return false;
  253. }
  254. // Make sure settings dependent on the display refresh rate are updated properly.
  255. updateVideoSettings();
  256. return true;
  257. }
  258. ///////////////////////////////////////////////////////////////////////////////////////////////////
  259. void PlayerComponent::onRestoreDisplay()
  260. {
  261. // If the player will in fact start another file (or is playing one), don't restore.
  262. if (mpv::qt::get_property_variant(m_mpv, "idle").toBool())
  263. DisplayComponent::Get().restorePreviousVideoMode();
  264. }
  265. ///////////////////////////////////////////////////////////////////////////////////////////////////
  266. void PlayerComponent::onRefreshRateChange()
  267. {
  268. // Make sure settings dependent on the display refresh rate are updated properly.
  269. updateVideoSettings();
  270. }
  271. ///////////////////////////////////////////////////////////////////////////////////////////////////
  272. void PlayerComponent::handleMpvEvent(mpv_event *event)
  273. {
  274. switch (event->event_id)
  275. {
  276. case MPV_EVENT_START_FILE:
  277. {
  278. m_currentUrl = mpv::qt::get_property_variant(m_mpv, "path").toString();
  279. m_playbackStartSent = false;
  280. break;
  281. }
  282. case MPV_EVENT_FILE_LOADED:
  283. {
  284. emit playing(m_currentUrl);
  285. break;
  286. }
  287. case MPV_EVENT_END_FILE:
  288. {
  289. mpv_event_end_file *endFile = (mpv_event_end_file *)event->data;
  290. switch (endFile->reason)
  291. {
  292. case MPV_END_FILE_REASON_EOF:
  293. emit finished(m_currentUrl);
  294. break;
  295. case MPV_END_FILE_REASON_ERROR:
  296. emit error(endFile->error, mpv_error_string(endFile->error));
  297. break;
  298. default:
  299. emit stopped(m_currentUrl);
  300. break;
  301. }
  302. emit playbackEnded(m_currentUrl);
  303. m_currentUrl = "";
  304. if (!m_streamSwitchImminent)
  305. m_restoreDisplayTimer.start(0);
  306. m_streamSwitchImminent = false;
  307. break;
  308. }
  309. case MPV_EVENT_IDLE:
  310. {
  311. emit playbackAllDone();
  312. break;
  313. }
  314. case MPV_EVENT_PLAYBACK_RESTART:
  315. {
  316. // it's also sent after seeks are completed
  317. if (!m_playbackStartSent)
  318. emit playbackStarting();
  319. m_playbackStartSent = true;
  320. break;
  321. }
  322. case MPV_EVENT_PROPERTY_CHANGE:
  323. {
  324. mpv_event_property *prop = (mpv_event_property *)event->data;
  325. if (strcmp(prop->name, "pause") == 0 && prop->format == MPV_FORMAT_FLAG)
  326. {
  327. int state = *(int *)prop->data;
  328. emit paused(state);
  329. }
  330. else if (strcmp(prop->name, "core-idle") == 0 && prop->format == MPV_FORMAT_FLAG)
  331. {
  332. emit playbackActive(!*(int *)prop->data);
  333. }
  334. else if (strcmp(prop->name, "cache-buffering-state") == 0 && prop->format == MPV_FORMAT_INT64)
  335. {
  336. int64_t percentage = *(int64_t *)prop->data;
  337. emit buffering(percentage);
  338. }
  339. else if (strcmp(prop->name, "playback-time") == 0 && prop->format == MPV_FORMAT_DOUBLE)
  340. {
  341. double pos = *(double*)prop->data;
  342. if (fabs(pos - m_lastPositionUpdate) > 0.015)
  343. {
  344. quint64 ms = (quint64)(qMax(pos * 1000.0, 0.0));
  345. emit positionUpdate(ms);
  346. m_lastPositionUpdate = pos;
  347. }
  348. }
  349. else if (strcmp(prop->name, "vo-configured") == 0)
  350. {
  351. int state = prop->format == MPV_FORMAT_FLAG ? *(int *)prop->data : 0;
  352. emit windowVisible(state);
  353. }
  354. else if (strcmp(prop->name, "duration") == 0)
  355. {
  356. if (prop->format == MPV_FORMAT_DOUBLE)
  357. emit updateDuration(*(double *)prop->data * 1000.0);
  358. }
  359. else if (strcmp(prop->name, "audio-device-list") == 0)
  360. {
  361. updateAudioDeviceList();
  362. }
  363. break;
  364. }
  365. case MPV_EVENT_LOG_MESSAGE:
  366. {
  367. mpv_event_log_message *msg = (mpv_event_log_message *)event->data;
  368. // Strip the trailing '\n'
  369. size_t len = strlen(msg->text);
  370. if (len > 0 && msg->text[len - 1] == '\n')
  371. len -= 1;
  372. QString logline = QString::fromUtf8(msg->prefix) + ": " + QString::fromUtf8(msg->text, len);
  373. if (msg->log_level >= MPV_LOG_LEVEL_V)
  374. QLOG_DEBUG() << qPrintable(logline);
  375. else if (msg->log_level >= MPV_LOG_LEVEL_INFO)
  376. QLOG_INFO() << qPrintable(logline);
  377. else if (msg->log_level >= MPV_LOG_LEVEL_WARN)
  378. QLOG_WARN() << qPrintable(logline);
  379. else
  380. QLOG_ERROR() << qPrintable(logline);
  381. break;
  382. }
  383. case MPV_EVENT_CLIENT_MESSAGE:
  384. {
  385. mpv_event_client_message *msg = (mpv_event_client_message *)event->data;
  386. if (msg->num_args < 3 || strcmp(msg->args[0], "hook_run") != 0)
  387. break;
  388. QString resumeId = QString::fromUtf8(msg->args[2]);
  389. // Start "on_load" hook.
  390. // This happens when the player is about to load the file, but no actual loading has taken part yet.
  391. // We use this to block loading until we explicitly tell it to continue.
  392. if (!strcmp(msg->args[1], "1"))
  393. {
  394. // Calling this lambda will instruct mpv to continue loading the file.
  395. auto resume = [=] {
  396. QLOG_INFO() << "resuming loading";
  397. mpv::qt::command_variant(m_mpv, QStringList() << "hook-ack" << resumeId);
  398. };
  399. if (switchDisplayFrameRate())
  400. {
  401. // Now wait for some time for mode change - this is needed because mode changing can take some
  402. // time, during which the screen is black, and initializing hardware decoding could fail due
  403. // to various strange OS-related reasons.
  404. // (Better hope the user doesn't try to exit Konvergo during mode change.)
  405. int pause = SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, "refreshrate.delay").toInt() * 1000;
  406. QLOG_INFO() << "waiting" << pause << "msec after rate switch before loading";
  407. QTimer::singleShot(pause, resume);
  408. }
  409. else
  410. {
  411. resume();
  412. }
  413. break;
  414. }
  415. // Start "on_preload" hook.
  416. // Used to probe codecs.
  417. if (!strcmp(msg->args[1], "2"))
  418. {
  419. startCodecsLoading([=] {
  420. mpv::qt::command_variant(m_mpv, QStringList() << "hook-ack" << resumeId);
  421. });
  422. break;
  423. }
  424. break;
  425. }
  426. default:; /* ignore */
  427. }
  428. }
  429. ///////////////////////////////////////////////////////////////////////////////////////////////////
  430. void PlayerComponent::handleMpvEvents()
  431. {
  432. // Process all events, until the event queue is empty.
  433. while (1)
  434. {
  435. mpv_event *event = mpv_wait_event(m_mpv, 0);
  436. if (event->event_id == MPV_EVENT_NONE)
  437. break;
  438. handleMpvEvent(event);
  439. }
  440. }
  441. ///////////////////////////////////////////////////////////////////////////////////////////////////
  442. void PlayerComponent::setVideoOnlyMode(bool enable)
  443. {
  444. if (m_window)
  445. {
  446. QQuickItem *web = m_window->findChild<QQuickItem *>("web");
  447. if (web)
  448. web->setVisible(!enable);
  449. }
  450. }
  451. ///////////////////////////////////////////////////////////////////////////////////////////////////
  452. void PlayerComponent::play()
  453. {
  454. QStringList args = (QStringList() << "set" << "pause" << "no");
  455. mpv::qt::command_variant(m_mpv, args);
  456. }
  457. ///////////////////////////////////////////////////////////////////////////////////////////////////
  458. void PlayerComponent::stop()
  459. {
  460. QStringList args("stop");
  461. mpv::qt::command_variant(m_mpv, args);
  462. }
  463. ///////////////////////////////////////////////////////////////////////////////////////////////////
  464. void PlayerComponent::clearQueue()
  465. {
  466. QStringList args("playlist_clear");
  467. mpv::qt::command_variant(m_mpv, args);
  468. }
  469. ///////////////////////////////////////////////////////////////////////////////////////////////////
  470. void PlayerComponent::pause()
  471. {
  472. QStringList args = (QStringList() << "set" << "pause" << "yes");
  473. mpv::qt::command_variant(m_mpv, args);
  474. }
  475. ///////////////////////////////////////////////////////////////////////////////////////////////////
  476. void PlayerComponent::seekTo(qint64 ms)
  477. {
  478. double timeSecs = ms / 1000.0;
  479. QVariantList args = (QVariantList() << "seek" << timeSecs << "absolute+exact");
  480. mpv::qt::command_variant(m_mpv, args);
  481. }
  482. ///////////////////////////////////////////////////////////////////////////////////////////////////
  483. QVariant PlayerComponent::getAudioDeviceList()
  484. {
  485. return mpv::qt::get_property_variant(m_mpv, "audio-device-list");
  486. }
  487. ///////////////////////////////////////////////////////////////////////////////////////////////////
  488. void PlayerComponent::setAudioDevice(const QString& name)
  489. {
  490. mpv::qt::set_property_variant(m_mpv, "audio-device", name);
  491. }
  492. ///////////////////////////////////////////////////////////////////////////////////////////////////
  493. void PlayerComponent::setVolume(int volume)
  494. {
  495. // Will fail if no audio output opened (i.e. no file playing)
  496. mpv::qt::set_property_variant(m_mpv, "volume", volume);
  497. }
  498. ///////////////////////////////////////////////////////////////////////////////////////////////////
  499. int PlayerComponent::volume()
  500. {
  501. QVariant volume = mpv::qt::get_property_variant(m_mpv, "volume");
  502. if (volume.isValid())
  503. return volume.toInt();
  504. return 0;
  505. }
  506. ///////////////////////////////////////////////////////////////////////////////////////////////////
  507. void PlayerComponent::setMuted(bool muted)
  508. {
  509. // Will fail if no audio output opened (i.e. no file playing)
  510. mpv::qt::set_property_variant(m_mpv, "mute", muted);
  511. }
  512. ///////////////////////////////////////////////////////////////////////////////////////////////////
  513. bool PlayerComponent::muted()
  514. {
  515. QVariant mute = mpv::qt::get_property_variant(m_mpv, "mute");
  516. if (mute.isValid())
  517. return mute.toBool();
  518. return false;
  519. }
  520. ///////////////////////////////////////////////////////////////////////////////////////////////////
  521. void PlayerComponent::setSubtitleStream(const QString &subtitleStream)
  522. {
  523. if (subtitleStream.isEmpty())
  524. {
  525. mpv::qt::set_property_variant(m_mpv, "ff-sid", "no");
  526. }
  527. else if (subtitleStream.startsWith("#"))
  528. {
  529. mpv::qt::set_property_variant(m_mpv, "ff-sid", subtitleStream.mid(1));
  530. }
  531. else
  532. {
  533. QStringList args = (QStringList() << "sub-add" << subtitleStream << "cached");
  534. mpv::qt::command_variant(m_mpv, args);
  535. }
  536. }
  537. ///////////////////////////////////////////////////////////////////////////////////////////////////
  538. void PlayerComponent::setAudioStream(const QString &audioStream)
  539. {
  540. if (audioStream.isEmpty())
  541. mpv::qt::set_property_variant(m_mpv, "ff-aid", "no");
  542. else
  543. mpv::qt::set_property_variant(m_mpv, "ff-aid", audioStream);
  544. }
  545. /////////////////////////////////////////////////////////////////////////////////////////
  546. void PlayerComponent::setAudioDelay(qint64 milliseconds)
  547. {
  548. m_playbackAudioDelay = milliseconds;
  549. double displayFps = DisplayComponent::Get().currentRefreshRate();
  550. const char *audioDelaySetting = "audio_delay.normal";
  551. if (fabs(displayFps - 24) < 1) // cover 24Hz, 23.976Hz, and values very close
  552. audioDelaySetting = "audio_delay.24hz";
  553. double fixedDelay = SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, audioDelaySetting).toFloat();
  554. mpv::qt::set_option_variant(m_mpv, "audio-delay", (fixedDelay + m_playbackAudioDelay) / 1000.0);
  555. }
  556. /////////////////////////////////////////////////////////////////////////////////////////
  557. void PlayerComponent::setSubtitleDelay(qint64 milliseconds)
  558. {
  559. mpv::qt::set_option_variant(m_mpv, "sub-delay", milliseconds / 1000.0);
  560. }
  561. ///////////////////////////////////////////////////////////////////////////////////////////////////
  562. void PlayerComponent::onReloadAudio()
  563. {
  564. mpv::qt::command_variant(m_mpv, QStringList() << "ao-reload");
  565. }
  566. ///////////////////////////////////////////////////////////////////////////////////////////////////
  567. // This is called with the set of previous audio devices that were detected, and the set of current
  568. // audio devices. From this we guess whether we should reopen the audio device. If the user-selected
  569. // device went away previously, and now comes back, reinitializing the player's audio output will
  570. // force the player and/or the OS to move audio output back to the user-selected device.
  571. void PlayerComponent::checkCurrentAudioDevice(const QSet<QString>& old_devs, const QSet<QString>& new_devs)
  572. {
  573. QString userDevice = SettingsComponent::Get().value(SETTINGS_SECTION_AUDIO, "device").toString();
  574. QSet<QString> removed = old_devs - new_devs;
  575. QSet<QString> added = new_devs - old_devs;
  576. QLOG_DEBUG() << "Audio devices removed:" << removed;
  577. QLOG_DEBUG() << "Audio devices added:" << added;
  578. QLOG_DEBUG() << "Audio device selected:" << userDevice;
  579. if (!mpv::qt::get_property_variant(m_mpv, "idle").toBool() && userDevice.length())
  580. {
  581. if (added.contains(userDevice))
  582. {
  583. // The timer is for debouncing the reload. Several change notifications could
  584. // come in quick succession. Also, it's possible that trying to open the
  585. // reappeared audio device immediately can fail.
  586. m_reloadAudioTimer.start(500);
  587. }
  588. }
  589. }
  590. ///////////////////////////////////////////////////////////////////////////////////////////////////
  591. void PlayerComponent::updateAudioDeviceList()
  592. {
  593. QVariantList settingList;
  594. QVariant list = getAudioDeviceList();
  595. QSet<QString> devices;
  596. for(const QVariant& d : list.toList())
  597. {
  598. Q_ASSERT(d.type() == QVariant::Map);
  599. QVariantMap dmap = d.toMap();
  600. devices.insert(dmap["name"].toString());
  601. QVariantMap entry;
  602. entry["value"] = dmap["name"];
  603. entry["title"] = dmap["description"];
  604. settingList << entry;
  605. }
  606. SettingsComponent::Get().updatePossibleValues(SETTINGS_SECTION_AUDIO, "device", settingList);
  607. checkCurrentAudioDevice(m_audioDevices, devices);
  608. m_audioDevices = devices;
  609. }
  610. ///////////////////////////////////////////////////////////////////////////////////////////////////
  611. void PlayerComponent::setAudioConfiguration()
  612. {
  613. QStringList aoDefaults;
  614. QString deviceType = SettingsComponent::Get().value(SETTINGS_SECTION_AUDIO, "devicetype").toString();
  615. if (SettingsComponent::Get().value(SETTINGS_SECTION_AUDIO, "exclusive").toBool())
  616. {
  617. aoDefaults << "wasapi:exclusive=yes";
  618. aoDefaults << "coreaudio:exclusive=yes";
  619. }
  620. mpv::qt::set_option_variant(m_mpv, "ao-defaults", aoDefaults.join(','));
  621. // set the audio device
  622. QVariant device = SettingsComponent::Get().value(SETTINGS_SECTION_AUDIO, "device");
  623. mpv::qt::set_property_variant(m_mpv, "audio-device", device);
  624. QString resampleOpts = "";
  625. bool normalize = SettingsComponent::Get().value(SETTINGS_SECTION_AUDIO, "normalize").toBool();
  626. resampleOpts += QString(":normalize=") + (normalize ? "yes" : "no");
  627. // Make downmix more similar to PHT.
  628. resampleOpts += ":o=[surround_mix_level=1]";
  629. mpv::qt::set_option_variant(m_mpv, "af-defaults", "lavrresample" + resampleOpts);
  630. m_passthroughCodecs.clear();
  631. // passthrough doesn't make sense with basic type
  632. if (deviceType != AUDIO_DEVICE_TYPE_BASIC)
  633. {
  634. SettingsSection* audioSection = SettingsComponent::Get().getSection(SETTINGS_SECTION_AUDIO);
  635. QStringList codecs;
  636. if (deviceType == AUDIO_DEVICE_TYPE_SPDIF)
  637. codecs = AudioCodecsSPDIF();
  638. else if (deviceType == AUDIO_DEVICE_TYPE_HDMI && audioSection->value("advanced").toBool())
  639. codecs = AudioCodecsAll();
  640. for(const QString& key : codecs)
  641. {
  642. if (audioSection->value("passthrough." + key).toBool())
  643. m_passthroughCodecs << key;
  644. }
  645. // dts-hd includes dts, but listing dts before dts-hd may disable dts-hd.
  646. if (m_passthroughCodecs.indexOf("dts-hd") != -1)
  647. m_passthroughCodecs.removeAll("dts");
  648. }
  649. QString passthroughCodecs = m_passthroughCodecs.join(",");
  650. mpv::qt::set_option_variant(m_mpv, "audio-spdif", passthroughCodecs);
  651. // set the channel layout
  652. QVariant layout = SettingsComponent::Get().value(SETTINGS_SECTION_AUDIO, "channels");
  653. // always force either stereo or transcoding
  654. if (deviceType == AUDIO_DEVICE_TYPE_SPDIF)
  655. layout = "2.0";
  656. mpv::qt::set_option_variant(m_mpv, "audio-channels", layout);
  657. // if the user has indicated that PCM only works for stereo, and that
  658. // the receiver supports AC3, set this extra option that allows us to transcode
  659. // 5.1 audio into a usable format, note that we only support AC3
  660. // here for now. We might need to add support for DTS transcoding
  661. // if we see user requests for it.
  662. //
  663. m_doAc3Transcoding = false;
  664. if (layout == "2.0" &&
  665. SettingsComponent::Get().value(SETTINGS_SECTION_AUDIO, "passthrough.ac3").toBool())
  666. {
  667. mpv::qt::command_variant(m_mpv, QStringList() << "af" << "add" << "@ac3:lavcac3enc");
  668. m_doAc3Transcoding = true;
  669. }
  670. else
  671. {
  672. mpv::qt::command_variant(m_mpv, QStringList() << "af" << "del" << "@ac3");
  673. }
  674. // Make a informational log message.
  675. QString audioConfig = QString(QString("Audio Config - device: %1, ") +
  676. "channel layout: %2, " +
  677. "passthrough codecs: %3, " +
  678. "ac3 transcoding: %4").arg(device.toString(),
  679. layout.toString(),
  680. passthroughCodecs.isEmpty() ? "none" : passthroughCodecs,
  681. m_doAc3Transcoding ? "yes" : "no");
  682. QLOG_INFO() << qPrintable(audioConfig);
  683. }
  684. ///////////////////////////////////////////////////////////////////////////////////////////////////
  685. void PlayerComponent::updateSubtitleSettings()
  686. {
  687. QVariant size = SettingsComponent::Get().value(SETTINGS_SECTION_SUBTITLES, "size");
  688. mpv::qt::set_option_variant(m_mpv, "sub-text-font-size", size);
  689. QVariant colorsString = SettingsComponent::Get().value(SETTINGS_SECTION_SUBTITLES, "color");
  690. auto colors = colorsString.toString().split(",");
  691. if (colors.length() == 2)
  692. {
  693. mpv::qt::set_option_variant(m_mpv, "sub-text-color", colors[0]);
  694. mpv::qt::set_option_variant(m_mpv, "sub-text-border-color", colors[1]);
  695. }
  696. QVariant subposString = SettingsComponent::Get().value(SETTINGS_SECTION_SUBTITLES, "placement");
  697. auto subpos = subposString.toString().split(",");
  698. if (subpos.length() == 2)
  699. {
  700. mpv::qt::set_option_variant(m_mpv, "sub-text-align-x", subpos[0]);
  701. mpv::qt::set_option_variant(m_mpv, "sub-text-align-y", subpos[1]);
  702. }
  703. }
  704. ///////////////////////////////////////////////////////////////////////////////////////////////////
  705. void PlayerComponent::updateVideoSettings()
  706. {
  707. QVariant syncMode = SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, "sync_mode");
  708. mpv::qt::set_option_variant(m_mpv, "video-sync", syncMode);
  709. QVariant hardwareDecoding = SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, "hardware_decoding");
  710. mpv::qt::set_property_variant(m_mpv, "hwdec", hardwareDecoding.toBool() ? "auto" : "no");
  711. QVariant deinterlace = SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, "deinterlace");
  712. mpv::qt::set_option_variant(m_mpv, "deinterlace", deinterlace.toBool() ? "yes" : "no");
  713. #ifndef TARGET_RPI
  714. double displayFps = DisplayComponent::Get().currentRefreshRate();
  715. mpv::qt::set_property_variant(m_mpv, "display-fps", displayFps);
  716. #endif
  717. setAudioDelay(m_playbackAudioDelay);
  718. QVariant cache = SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, "cache");
  719. mpv::qt::set_option_variant(m_mpv, "cache", cache.toInt() * 1024);
  720. }
  721. /////////////////////////////////////////////////////////////////////////////////////////
  722. void PlayerComponent::userCommand(QString command)
  723. {
  724. QByteArray cmdUtf8 = command.toUtf8();
  725. mpv_command_string(m_mpv, cmdUtf8.data());
  726. }
  727. /////////////////////////////////////////////////////////////////////////////////////////
  728. void PlayerComponent::initializeCodecSupport()
  729. {
  730. QMap<QString, QString> all = { {"vc1", "WVC1"}, {"mpeg2video", "MPG2"} };
  731. for (auto name : all.keys())
  732. {
  733. bool ok = true;
  734. #ifdef TARGET_RPI
  735. char res[100] = "";
  736. bcm_host_init();
  737. if (vc_gencmd(res, sizeof(res), "codec_enabled %s", all[name].toUtf8().data()))
  738. res[0] = '\0'; // error
  739. ok = !!strstr(res, "=enabled");
  740. #endif
  741. m_codecSupport[name] = ok;
  742. QLOG_INFO() << "Codec" << name << (ok ? "present" : "disabled");
  743. }
  744. }
  745. /////////////////////////////////////////////////////////////////////////////////////////
  746. bool PlayerComponent::checkCodecSupport(const QString& codec)
  747. {
  748. if (m_codecSupport.contains(codec))
  749. return m_codecSupport[codec];
  750. return true; // doesn't matter if unknown codecs are reported as "ok"
  751. }
  752. /////////////////////////////////////////////////////////////////////////////////////////
  753. QList<CodecDriver> convertCodecList(QVariant list, CodecType type)
  754. {
  755. QList<CodecDriver> codecs;
  756. foreach (const QVariant& e, list.toList())
  757. {
  758. QVariantMap map = e.toMap();
  759. QString family = map["family"].toString();
  760. QString codec = map["codec"].toString();
  761. QString driver = map["driver"].toString();
  762. // Only include FFmpeg codecs; exclude pseudo-codecs like spdif.
  763. if (family != "lavc")
  764. continue;
  765. CodecDriver ncodec = {};
  766. ncodec.type = type;
  767. ncodec.format = codec;
  768. ncodec.driver = driver;
  769. ncodec.present = true;
  770. codecs.append(ncodec);
  771. }
  772. return codecs;
  773. }
  774. /////////////////////////////////////////////////////////////////////////////////////////
  775. QList<CodecDriver> PlayerComponent::installedCodecs()
  776. {
  777. QList<CodecDriver> codecs;
  778. codecs.append(convertCodecList(mpv::qt::get_property_variant(m_mpv, "decoder-list"), CodecType::Decoder));
  779. codecs.append(convertCodecList(mpv::qt::get_property_variant(m_mpv, "encoder-list"), CodecType::Encoder));
  780. return codecs;
  781. }
  782. /////////////////////////////////////////////////////////////////////////////////////////
  783. PlaybackInfo PlayerComponent::getPlaybackInfo()
  784. {
  785. PlaybackInfo info = {};
  786. for (auto codec : m_passthroughCodecs)
  787. {
  788. // Normalize back to canonical codec names.
  789. if (codec == "dts-hd")
  790. codec = "dts";
  791. info.audioPassthroughCodecs.insert(codec);
  792. }
  793. info.enableAC3Transcoding = m_doAc3Transcoding;
  794. auto tracks = mpv::qt::get_property_variant(m_mpv, "track-list");
  795. foreach (const QVariant& track, tracks.toList())
  796. {
  797. QVariantMap map = track.toMap();
  798. QString type = map["type"].toString();
  799. StreamInfo stream = {};
  800. stream.isVideo = type == "video";
  801. stream.isAudio = type == "audio";
  802. stream.codec = map["codec"].toString();
  803. stream.audioChannels = map["demux-channel-count"].toInt();
  804. stream.videoResolution = QSize(map["demux-w"].toInt(), map["demux-h"].toInt());
  805. if (stream.isVideo)
  806. {
  807. // Assume there's only 1 video stream. We get the profile from the
  808. // server because mpv can't be bothered to determine it.
  809. stream.profile = m_serverMediaInfo["videoProfile"].toString();
  810. }
  811. info.streams.append(stream);
  812. }
  813. return info;
  814. }
  815. /////////////////////////////////////////////////////////////////////////////////////////
  816. void PlayerComponent::setPreferredCodecs(const QList<CodecDriver>& codecs)
  817. {
  818. QStringList items;
  819. for (auto codec : codecs)
  820. {
  821. if (codec.type == CodecType::Decoder)
  822. {
  823. items << QString("lavc:") + codec.driver;
  824. }
  825. }
  826. QString opt = items.join(",");
  827. // For simplicity, we don't distinguish between audio and video. The player
  828. // will ignore entries with mismatching media type.
  829. mpv::qt::set_option_variant(m_mpv, "ad", opt);
  830. mpv::qt::set_option_variant(m_mpv, "vd", opt);
  831. }
  832. // For QVariant.
  833. Q_DECLARE_METATYPE(std::function<void()>);
  834. /////////////////////////////////////////////////////////////////////////////////////////
  835. void PlayerComponent::startCodecsLoading(std::function<void()> resume)
  836. {
  837. auto fetcher = new CodecsFetcher();
  838. fetcher->userData = QVariant::fromValue(resume);
  839. connect(fetcher, &CodecsFetcher::done, this, &PlayerComponent::onCodecsLoadingDone);
  840. Codecs::updateCachedCodecList();
  841. QList<CodecDriver> codecs = Codecs::determineRequiredCodecs(getPlaybackInfo());
  842. setPreferredCodecs(codecs);
  843. fetcher->installCodecs(codecs);
  844. }
  845. /////////////////////////////////////////////////////////////////////////////////////////
  846. void PlayerComponent::onCodecsLoadingDone(CodecsFetcher* sender)
  847. {
  848. sender->deleteLater();
  849. sender->userData.value<std::function<void()>>()();
  850. }
  851. /////////////////////////////////////////////////////////////////////////////////////////
  852. static QString get_mpv_osd(mpv_handle *ctx, const QString& property)
  853. {
  854. char *s = mpv_get_property_osd_string(ctx, property.toUtf8().data());
  855. if (!s)
  856. return "-";
  857. QString r = QString::fromUtf8(s);
  858. mpv_free(s);
  859. return r;
  860. }
  861. #define MPV_PROPERTY(p) get_mpv_osd(m_mpv, p)
  862. #define MPV_PROPERTY_BOOL(p) (mpv::qt::get_property_variant(m_mpv, p).toBool())
  863. /////////////////////////////////////////////////////////////////////////////////////////
  864. void PlayerComponent::appendAudioFormat(QTextStream& info, const QString& property) const
  865. {
  866. // Guess if it's a passthrough format. Don't show the channel layout in this
  867. // case, because it's confusing.
  868. QString audioFormat = MPV_PROPERTY(property + "/format");
  869. if (audioFormat.startsWith("spdif-"))
  870. {
  871. info << "passthrough (" << audioFormat.mid(6) << ")";
  872. }
  873. else
  874. {
  875. QString hr = MPV_PROPERTY(property + "/hr-channels");
  876. QString full = MPV_PROPERTY(property + "/channels");
  877. info << hr;
  878. if (hr != full)
  879. info << " (" << full << ")";
  880. }
  881. }
  882. /////////////////////////////////////////////////////////////////////////////////////////
  883. QString PlayerComponent::videoInformation() const
  884. {
  885. QString infoStr;
  886. QTextStream info(&infoStr);
  887. // check if video is playing
  888. if (mpv::qt::get_property_variant(m_mpv, "idle").toBool())
  889. return "";
  890. info << "File:" << endl;
  891. info << "URL: " << MPV_PROPERTY("path") << endl;
  892. info << "Container: " << MPV_PROPERTY("file-format") << endl;
  893. info << "Native seeking: " << ((MPV_PROPERTY_BOOL("seekable") &&
  894. !MPV_PROPERTY_BOOL("partially-seekable"))
  895. ? "yes" : "no") << endl;
  896. info << endl;
  897. info << "Video:" << endl;
  898. info << "Codec: " << MPV_PROPERTY("video-codec") << endl;
  899. info << "Size: " << MPV_PROPERTY("video-params/dw") << "x"
  900. << MPV_PROPERTY("video-params/dh") << endl;
  901. info << "FPS (container): " << MPV_PROPERTY("fps") << endl;
  902. info << "FPS (filters): " << MPV_PROPERTY("estimated-vf-fps") << endl;
  903. info << "Aspect: " << MPV_PROPERTY("video-aspect") << endl;
  904. info << "Bitrate: " << MPV_PROPERTY("video-bitrate") << endl;
  905. double displayFps = DisplayComponent::Get().currentRefreshRate();
  906. info << "Display FPS: " << MPV_PROPERTY("display-fps")
  907. << " (" << displayFps << ")" << endl;
  908. info << "Hardware Decoding: " << MPV_PROPERTY("hwdec-active")
  909. << " (" << MPV_PROPERTY("hwdec-detected") << ")" << endl;
  910. info << endl;
  911. info << "Audio: " << endl;
  912. info << "Codec: " << MPV_PROPERTY("audio-codec") << endl;
  913. info << "Bitrate: " << MPV_PROPERTY("audio-bitrate") << endl;
  914. info << "Channels (input): ";
  915. appendAudioFormat(info, "audio-params");
  916. info << endl;
  917. info << "Channels (output): ";
  918. appendAudioFormat(info, "audio-out-params");
  919. info << endl;
  920. info << endl;
  921. info << "Performance: " << endl;
  922. info << "A/V: " << MPV_PROPERTY("avsync") << endl;
  923. info << "Dropped frames: " << MPV_PROPERTY("vo-drop-frame-count") << endl;
  924. bool dispSync = MPV_PROPERTY_BOOL("display-sync-active");
  925. info << "Display Sync: ";
  926. if (!dispSync)
  927. {
  928. info << "no" << endl;
  929. }
  930. else
  931. {
  932. info << "yes (ratio " << MPV_PROPERTY("vsync-ratio") << ")" << endl;
  933. info << "Mistimed frames: " << MPV_PROPERTY("mistimed-frame-count")
  934. << "/" << MPV_PROPERTY("vo-delayed-frame-count") << endl;
  935. info << "Measured FPS: " << MPV_PROPERTY("estimated-display-fps")
  936. << " (" << MPV_PROPERTY("vsync-jitter") << ")" << endl;
  937. info << "V. speed corr.: " << MPV_PROPERTY("video-speed-correction") << endl;
  938. info << "A. speed corr.: " << MPV_PROPERTY("audio-speed-correction") << endl;
  939. }
  940. info << endl;
  941. info << "Cache:" << endl;
  942. info << "Seconds: " << MPV_PROPERTY("demuxer-cache-duration") << endl;
  943. info << "Extra readahead: " << MPV_PROPERTY("cache-used") << endl;
  944. info << "Buffering: " << MPV_PROPERTY("cache-buffering-state") << endl;
  945. info << endl;
  946. info << "Misc: " << endl;
  947. info << "Time: " << MPV_PROPERTY("playback-time") << " / "
  948. << MPV_PROPERTY("duration")
  949. << " (" << MPV_PROPERTY("percent-pos") << "%)" << endl;
  950. info << "State: " << (MPV_PROPERTY_BOOL("pause") ? "paused " : "")
  951. << (MPV_PROPERTY_BOOL("paused-for-cache") ? "buffering " : "")
  952. << (MPV_PROPERTY_BOOL("core-idle") ? "waiting " : "playing ")
  953. << (MPV_PROPERTY_BOOL("seeking") ? "seeking " : "")
  954. << endl;
  955. info << flush;
  956. return infoStr;
  957. }