PlayerComponent.cpp 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304
  1. #include "PlayerComponent.h"
  2. #include <QString>
  3. #include <Qt>
  4. #include <QDir>
  5. #include <QCoreApplication>
  6. #include <QGuiApplication>
  7. #include "display/DisplayComponent.h"
  8. #include "settings/SettingsComponent.h"
  9. #include "system/SystemComponent.h"
  10. #include "utils/Utils.h"
  11. #include "utils/Log.h"
  12. #include "ComponentManager.h"
  13. #include "settings/SettingsSection.h"
  14. #include "PlayerQuickItem.h"
  15. #include "input/InputComponent.h"
  16. #include "QsLog.h"
  17. #include <math.h>
  18. #include <string.h>
  19. #include <shared/Paths.h>
  20. #ifdef TARGET_RPI
  21. #include <bcm_host.h>
  22. #include <interface/vmcs_host/vcgencmd.h>
  23. #endif
  24. ///////////////////////////////////////////////////////////////////////////////////////////////////
  25. static void wakeup_cb(void *context)
  26. {
  27. PlayerComponent *player = (PlayerComponent *)context;
  28. emit player->onMpvEvents();
  29. }
  30. ///////////////////////////////////////////////////////////////////////////////////////////////////
  31. PlayerComponent::PlayerComponent(QObject* parent)
  32. : ComponentBase(parent), m_state(State::stopped), m_paused(false), m_playbackActive(false),
  33. m_inPlayback(false), m_bufferingPercentage(100), m_lastBufferingPercentage(-1),
  34. m_lastPositionUpdate(0.0), m_playbackAudioDelay(0),
  35. m_playbackStartSent(false), m_window(nullptr), m_mediaFrameRate(0),
  36. m_restoreDisplayTimer(this), m_reloadAudioTimer(this),
  37. m_streamSwitchImminent(false), m_doAc3Transcoding(false)
  38. {
  39. qmlRegisterType<PlayerQuickItem>("Konvergo", 1, 0, "MpvVideo"); // deprecated name
  40. qmlRegisterType<PlayerQuickItem>("Konvergo", 1, 0, "KonvergoVideo");
  41. m_restoreDisplayTimer.setSingleShot(true);
  42. connect(&m_restoreDisplayTimer, &QTimer::timeout, this, &PlayerComponent::onRestoreDisplay);
  43. connect(&DisplayComponent::Get(), &DisplayComponent::refreshRateChanged, this, &PlayerComponent::onRefreshRateChange);
  44. m_reloadAudioTimer.setSingleShot(true);
  45. connect(&m_reloadAudioTimer, &QTimer::timeout, this, &PlayerComponent::onReloadAudio);
  46. }
  47. /////////////////////////////////////////////////////////////////////////////////////////
  48. void PlayerComponent::componentPostInitialize()
  49. {
  50. InputComponent::Get().registerHostCommand("player", this, "userCommand");
  51. }
  52. ///////////////////////////////////////////////////////////////////////////////////////////////////
  53. PlayerComponent::~PlayerComponent()
  54. {
  55. if (m_mpv)
  56. mpv_set_wakeup_callback(m_mpv, nullptr, nullptr);
  57. }
  58. ///////////////////////////////////////////////////////////////////////////////////////////////////
  59. bool PlayerComponent::componentInitialize()
  60. {
  61. m_mpv = mpv::qt::Handle::FromRawHandle(mpv_create());
  62. if (!m_mpv)
  63. throw FatalException(tr("Failed to load mpv."));
  64. mpv_request_log_messages(m_mpv, "terminal-default");
  65. mpv_set_option_string(m_mpv, "msg-level", "all=v");
  66. // No mouse events
  67. mpv_set_option_string(m_mpv, "input-cursor", "no");
  68. mpv_set_option_string(m_mpv, "cursor-autohide", "no");
  69. mpv_set_option_string(m_mpv, "config", "yes");
  70. mpv_set_option_string(m_mpv, "config-dir", Paths::dataDir().toUtf8().data());
  71. // Disable native OSD if mpv_command_string() is used.
  72. mpv_set_option_string(m_mpv, "osd-level", "0");
  73. // This forces the player not to rebase playback time to 0 with mkv. We
  74. // require this, because mkv transcoding lets files start at times other
  75. // than 0, and web-client expects that we return these times unchanged.
  76. mpv::qt::set_option_variant(m_mpv, "demuxer-mkv-probe-start-time", false);
  77. // Always use the internal mixer by default.
  78. mpv_set_option_string(m_mpv, "softvol", "yes");
  79. // Just discard audio output if no audio device could be opened. This gives
  80. // us better flexibility how to react to such errors (instead of just
  81. // aborting playback immediately).
  82. mpv_set_option_string(m_mpv, "audio-fallback-to-null", "yes");
  83. // Do not let the decoder downmix (better customization for us).
  84. mpv::qt::set_option_variant(m_mpv, "ad-lavc-downmix", false);
  85. // Make it load the hwdec interop, so hwdec can be enabled at runtime.
  86. mpv::qt::set_option_variant(m_mpv, "hwdec-preload", "auto");
  87. // User-visible application name used by some audio APIs (at least PulseAudio).
  88. mpv_set_option_string(m_mpv, "audio-client-name", QCoreApplication::applicationName().toUtf8().data());
  89. // User-visible stream title used by some audio APIs (at least PulseAudio and wasapi).
  90. mpv_set_option_string(m_mpv, "title", QCoreApplication::applicationName().toUtf8().data());
  91. // Apply some low-memory settings on RPI, which is relatively memory-constrained.
  92. #ifdef TARGET_RPI
  93. // The backbuffer makes seeking back faster (without having to do a HTTP-level seek)
  94. mpv::qt::set_option_variant(m_mpv, "cache-backbuffer", 10 * 1024); // KB
  95. // The demuxer queue is used for the readahead, and also for dealing with badly
  96. // interlaved audio/video. Setting it too low increases sensitivity to network
  97. // issues, and could cause playback failure with "bad" files.
  98. mpv::qt::set_option_variant(m_mpv, "demuxer-max-bytes", 50 * 1024 * 1024); // bytes
  99. // Specifically for enabling mpeg4.
  100. mpv::qt::set_option_variant(m_mpv, "hwdec-codecs", "all");
  101. // Do not use exact seeks by default. (This affects the start position in the "loadfile"
  102. // command in particular. We override the seek mode for normal "seek" commands.)
  103. mpv::qt::set_option_variant(m_mpv, "hr-seek", "no");
  104. #endif
  105. mpv_observe_property(m_mpv, 0, "pause", MPV_FORMAT_FLAG);
  106. mpv_observe_property(m_mpv, 0, "core-idle", MPV_FORMAT_FLAG);
  107. mpv_observe_property(m_mpv, 0, "cache-buffering-state", MPV_FORMAT_INT64);
  108. mpv_observe_property(m_mpv, 0, "playback-time", MPV_FORMAT_DOUBLE);
  109. mpv_observe_property(m_mpv, 0, "vo-configured", MPV_FORMAT_FLAG);
  110. mpv_observe_property(m_mpv, 0, "duration", MPV_FORMAT_DOUBLE);
  111. mpv_observe_property(m_mpv, 0, "audio-device-list", MPV_FORMAT_NODE);
  112. connect(this, &PlayerComponent::onMpvEvents, this, &PlayerComponent::handleMpvEvents, Qt::QueuedConnection);
  113. mpv_set_wakeup_callback(m_mpv, wakeup_cb, this);
  114. if (mpv_initialize(m_mpv) < 0)
  115. throw FatalException(tr("Failed to initialize mpv."));
  116. // Setup a hook with the ID 1, which is run during the file is loaded.
  117. // Used to delay playback start for display framerate switching.
  118. // (See handler in handleMpvEvent() for details.)
  119. mpv::qt::command_variant(m_mpv, QStringList() << "hook-add" << "on_load" << "1" << "0");
  120. // Setup a hook with the ID 1, which is run at a certain stage during loading.
  121. // We use it to probe the codecs.
  122. mpv::qt::command_variant(m_mpv, QStringList() << "hook-add" << "on_preloaded" << "2" << "0");
  123. updateAudioDeviceList();
  124. setAudioConfiguration();
  125. updateSubtitleSettings();
  126. updateVideoSettings();
  127. connect(SettingsComponent::Get().getSection(SETTINGS_SECTION_VIDEO), &SettingsSection::valuesUpdated,
  128. this, &PlayerComponent::updateVideoSettings);
  129. connect(SettingsComponent::Get().getSection(SETTINGS_SECTION_SUBTITLES), &SettingsSection::valuesUpdated,
  130. this, &PlayerComponent::updateSubtitleSettings);
  131. connect(SettingsComponent::Get().getSection(SETTINGS_SECTION_AUDIO), &SettingsSection::valuesUpdated,
  132. this, &PlayerComponent::setAudioConfiguration);
  133. initializeCodecSupport();
  134. Codecs::updateCachedCodecList();
  135. QString codecInfo;
  136. for (auto codec : Codecs::getCachedCodecList())
  137. {
  138. if (codec.present)
  139. {
  140. if (codecInfo.size())
  141. codecInfo += " ";
  142. codecInfo += codec.driver;
  143. if (codec.type == CodecType::Encoder)
  144. codecInfo += "(enc)";
  145. }
  146. }
  147. QLOG_INFO() << "Present codecs:" << qPrintable(codecInfo);
  148. return true;
  149. }
  150. ///////////////////////////////////////////////////////////////////////////////////////////////////
  151. void PlayerComponent::setQtQuickWindow(QQuickWindow* window)
  152. {
  153. PlayerQuickItem* video = window->findChild<PlayerQuickItem*>("video");
  154. if (!video)
  155. throw FatalException(tr("Failed to load video element."));
  156. mpv_set_option_string(m_mpv, "vo", "opengl-cb");
  157. video->initMpv(this);
  158. }
  159. ///////////////////////////////////////////////////////////////////////////////////////////////////
  160. void PlayerComponent::setRpiWindow(QQuickWindow* window)
  161. {
  162. window->setFlags(Qt::FramelessWindowHint);
  163. mpv_set_option_string(m_mpv, "vo", "rpi");
  164. }
  165. ///////////////////////////////////////////////////////////////////////////////////////////////////
  166. void PlayerComponent::setWindow(QQuickWindow* window)
  167. {
  168. bool useRpi = false;
  169. #ifdef TARGET_RPI
  170. useRpi = true;
  171. #endif
  172. m_window = window;
  173. if (!window)
  174. return;
  175. QString forceVo = SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, "debug.force_vo").toString();
  176. if (forceVo.size())
  177. mpv::qt::set_option_variant(m_mpv, "vo", forceVo);
  178. else if (useRpi)
  179. setRpiWindow(window);
  180. else
  181. setQtQuickWindow(window);
  182. }
  183. ///////////////////////////////////////////////////////////////////////////////////////////////////
  184. bool PlayerComponent::load(const QString& url, const QVariantMap& options, const QVariantMap &metadata, const QString& audioStream , const QString& subtitleStream)
  185. {
  186. stop();
  187. queueMedia(url, options, metadata, audioStream, subtitleStream);
  188. return true;
  189. }
  190. ///////////////////////////////////////////////////////////////////////////////////////////////////
  191. void PlayerComponent::queueMedia(const QString& url, const QVariantMap& options, const QVariantMap &metadata, const QString& audioStream, const QString& subtitleStream)
  192. {
  193. m_mediaFrameRate = metadata["frameRate"].toFloat(); // returns 0 on failure
  194. m_serverMediaInfo = metadata["media"].toMap();
  195. updateVideoSettings();
  196. QVariantList command;
  197. command << "loadfile" << url;
  198. command << "append-play"; // if nothing is playing, play it now, otherwise just enqueue it
  199. QVariantMap extraArgs;
  200. quint64 startMilliseconds = options["startMilliseconds"].toLongLong();
  201. if (startMilliseconds != 0)
  202. extraArgs.insert("start", "+" + QString::number(startMilliseconds / 1000.0));
  203. // force default audio selection
  204. extraArgs.insert("ff-aid", "auto");
  205. // by default ignore all subtitles, unless overridden
  206. extraArgs.insert("sid", "no");
  207. extraArgs.insert("ff-sid", "auto");
  208. // detect subtitles
  209. if (!subtitleStream.isEmpty())
  210. {
  211. // If the stream title starts with a #, then it's an index
  212. if (subtitleStream.startsWith("#"))
  213. extraArgs.insert("ff-sid", subtitleStream.mid(1));
  214. else {
  215. extraArgs.insert("sub-file", subtitleStream);
  216. extraArgs.insert("sid", "auto"); // select the external one by default
  217. }
  218. }
  219. if (metadata["type"] == "music")
  220. extraArgs.insert("vid", "no");
  221. // and then the audio stream
  222. if (!audioStream.isEmpty())
  223. extraArgs.insert("ff-aid", audioStream);
  224. extraArgs.insert("pause", options["autoplay"].toBool() ? "no" : "yes");
  225. QString userAgent = metadata["headers"].toMap()["User-Agent"].toString();
  226. if (userAgent.size())
  227. extraArgs.insert("user-agent", userAgent);
  228. // Make sure the list of requested codecs is reset.
  229. extraArgs.insert("ad", "");
  230. extraArgs.insert("vd", "");
  231. command << extraArgs;
  232. QLOG_DEBUG() << command;
  233. mpv::qt::command_variant(m_mpv, command);
  234. }
  235. /////////////////////////////////////////////////////////////////////////////////////////
  236. void PlayerComponent::streamSwitch()
  237. {
  238. m_streamSwitchImminent = true;
  239. }
  240. ///////////////////////////////////////////////////////////////////////////////////////////////////
  241. bool PlayerComponent::switchDisplayFrameRate()
  242. {
  243. QLOG_DEBUG() << "Video framerate:" << m_mediaFrameRate << "fps";
  244. if (!SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, "refreshrate.auto_switch").toBool())
  245. {
  246. QLOG_DEBUG() << "Not switching refresh-rate (disabled by settings).";
  247. return false;
  248. }
  249. bool fs = SettingsComponent::Get().value(SETTINGS_SECTION_MAIN, "fullscreen").toBool();
  250. #if KONVERGO_OPENELEC
  251. fs = true;
  252. #endif
  253. if (!fs)
  254. {
  255. QLOG_DEBUG() << "Not switching refresh-rate (not in fullscreen mode).";
  256. return false;
  257. }
  258. if (m_mediaFrameRate < 1)
  259. {
  260. QLOG_DEBUG() << "Not switching refresh-rate (no known video framerate).";
  261. return false;
  262. }
  263. // Make sure a timer started by the previous file ending isn't accidentally
  264. // still in-flight. It could switch the display back after we've switched.
  265. m_restoreDisplayTimer.stop();
  266. DisplayComponent* display = &DisplayComponent::Get();
  267. if (!display->switchToBestVideoMode(m_mediaFrameRate))
  268. {
  269. QLOG_DEBUG() << "Switching refresh-rate failed or unnecessary.";
  270. return false;
  271. }
  272. // Make sure settings dependent on the display refresh rate are updated properly.
  273. updateVideoSettings();
  274. return true;
  275. }
  276. ///////////////////////////////////////////////////////////////////////////////////////////////////
  277. void PlayerComponent::onRestoreDisplay()
  278. {
  279. // If the player will in fact start another file (or is playing one), don't restore.
  280. if (mpv::qt::get_property_variant(m_mpv, "idle").toBool())
  281. DisplayComponent::Get().restorePreviousVideoMode();
  282. }
  283. ///////////////////////////////////////////////////////////////////////////////////////////////////
  284. void PlayerComponent::onRefreshRateChange()
  285. {
  286. // Make sure settings dependent on the display refresh rate are updated properly.
  287. updateVideoSettings();
  288. }
  289. ///////////////////////////////////////////////////////////////////////////////////////////////////
  290. void PlayerComponent::updatePlaybackState()
  291. {
  292. State newState = m_state;
  293. if (m_inPlayback) {
  294. if (m_paused)
  295. {
  296. newState = State::paused;
  297. }
  298. else if (m_playbackActive)
  299. {
  300. newState = State::playing;
  301. }
  302. else
  303. {
  304. // Playback not active, but also not buffering means we're in some "other"
  305. // waiting state. Pretend to web-client that we're buffering.
  306. if (m_bufferingPercentage == 100)
  307. m_bufferingPercentage = 0;
  308. newState = State::buffering;
  309. }
  310. }
  311. else
  312. {
  313. if (newState != State::error)
  314. newState = State::stopped;
  315. }
  316. if (newState != m_state)
  317. {
  318. switch (newState) {
  319. case State::paused:
  320. QLOG_INFO() << "Entering state: paused";
  321. emit paused();
  322. break;
  323. case State::playing:
  324. QLOG_INFO() << "Entering state: playing";
  325. emit playing();
  326. break;
  327. case State::stopped:
  328. QLOG_INFO() << "Entering state: stopped";
  329. emit stopped();
  330. break;
  331. case State::buffering:
  332. QLOG_INFO() << "Entering state: buffering";
  333. m_lastBufferingPercentage = -1; /* force update below */
  334. break;
  335. case State::error:
  336. /* handled separately */
  337. break;
  338. }
  339. m_state = newState;
  340. }
  341. if (m_state == State::buffering && m_lastBufferingPercentage != m_bufferingPercentage)
  342. emit buffering(m_bufferingPercentage);
  343. m_lastBufferingPercentage = m_bufferingPercentage;
  344. }
  345. ///////////////////////////////////////////////////////////////////////////////////////////////////
  346. void PlayerComponent::handleMpvEvent(mpv_event *event)
  347. {
  348. switch (event->event_id)
  349. {
  350. case MPV_EVENT_START_FILE:
  351. {
  352. m_playbackStartSent = false;
  353. m_inPlayback = true;
  354. break;
  355. }
  356. case MPV_EVENT_END_FILE:
  357. {
  358. mpv_event_end_file *endFile = (mpv_event_end_file *)event->data;
  359. if (endFile->reason == MPV_END_FILE_REASON_ERROR)
  360. {
  361. QLOG_INFO() << "Entering state: error";
  362. m_state = State::error;
  363. emit error(mpv_error_string(endFile->error));
  364. }
  365. m_inPlayback = false;
  366. if (!m_streamSwitchImminent)
  367. m_restoreDisplayTimer.start(0);
  368. m_streamSwitchImminent = false;
  369. break;
  370. }
  371. case MPV_EVENT_PLAYBACK_RESTART:
  372. {
  373. #if defined(Q_OS_MAC)
  374. // On OSX, initializing VideoTooolbox (hardware decoder API) will mysteriously
  375. // show the hidden mouse pointer again. The VTDecompressionSessionCreate API
  376. // function does this, and we have no influence over its behavior. To make sure
  377. // the cursor is gone again when starting playback, listen to the player's
  378. // playbackStarting signal, at which point decoder initialization is guaranteed
  379. // to be completed. Then we just have to set the cursor again on the Cocoa level.
  380. if (!m_playbackStartSent && QGuiApplication::overrideCursor())
  381. QGuiApplication::setOverrideCursor(QCursor(Qt::BlankCursor));
  382. #endif
  383. m_playbackStartSent = true;
  384. break;
  385. }
  386. case MPV_EVENT_PROPERTY_CHANGE:
  387. {
  388. mpv_event_property *prop = (mpv_event_property *)event->data;
  389. if (strcmp(prop->name, "pause") == 0 && prop->format == MPV_FORMAT_FLAG)
  390. {
  391. m_paused = !!*(int *)prop->data;
  392. }
  393. else if (strcmp(prop->name, "core-idle") == 0 && prop->format == MPV_FORMAT_FLAG)
  394. {
  395. m_playbackActive = !*(int *)prop->data;
  396. emit playbackActive(m_playbackActive);
  397. }
  398. else if (strcmp(prop->name, "cache-buffering-state") == 0)
  399. {
  400. m_bufferingPercentage = prop->format == MPV_FORMAT_INT64 ? *(int64_t *)prop->data : 100;
  401. }
  402. else if (strcmp(prop->name, "playback-time") == 0 && prop->format == MPV_FORMAT_DOUBLE)
  403. {
  404. double pos = *(double*)prop->data;
  405. if (fabs(pos - m_lastPositionUpdate) > 0.015)
  406. {
  407. quint64 ms = (quint64)(qMax(pos * 1000.0, 0.0));
  408. emit positionUpdate(ms);
  409. m_lastPositionUpdate = pos;
  410. }
  411. }
  412. else if (strcmp(prop->name, "vo-configured") == 0)
  413. {
  414. int state = prop->format == MPV_FORMAT_FLAG ? *(int *)prop->data : 0;
  415. emit windowVisible(state);
  416. }
  417. else if (strcmp(prop->name, "duration") == 0)
  418. {
  419. if (prop->format == MPV_FORMAT_DOUBLE)
  420. emit updateDuration(*(double *)prop->data * 1000.0);
  421. }
  422. else if (strcmp(prop->name, "audio-device-list") == 0)
  423. {
  424. updateAudioDeviceList();
  425. }
  426. break;
  427. }
  428. case MPV_EVENT_LOG_MESSAGE:
  429. {
  430. mpv_event_log_message *msg = (mpv_event_log_message *)event->data;
  431. // Strip the trailing '\n'
  432. size_t len = strlen(msg->text);
  433. if (len > 0 && msg->text[len - 1] == '\n')
  434. len -= 1;
  435. QString logline = QString::fromUtf8(msg->prefix) + ": " + QString::fromUtf8(msg->text, (int)len);
  436. if (msg->log_level >= MPV_LOG_LEVEL_V)
  437. QLOG_DEBUG() << qPrintable(logline);
  438. else if (msg->log_level >= MPV_LOG_LEVEL_INFO)
  439. QLOG_INFO() << qPrintable(logline);
  440. else if (msg->log_level >= MPV_LOG_LEVEL_WARN)
  441. QLOG_WARN() << qPrintable(logline);
  442. else
  443. QLOG_ERROR() << qPrintable(logline);
  444. break;
  445. }
  446. case MPV_EVENT_CLIENT_MESSAGE:
  447. {
  448. mpv_event_client_message *msg = (mpv_event_client_message *)event->data;
  449. if (msg->num_args < 3 || strcmp(msg->args[0], "hook_run") != 0)
  450. break;
  451. QString resumeId = QString::fromUtf8(msg->args[2]);
  452. // Start "on_load" hook.
  453. // This happens when the player is about to load the file, but no actual loading has taken part yet.
  454. // We use this to block loading until we explicitly tell it to continue.
  455. if (!strcmp(msg->args[1], "1"))
  456. {
  457. // Calling this lambda will instruct mpv to continue loading the file.
  458. auto resume = [=] {
  459. QLOG_INFO() << "checking codecs";
  460. startCodecsLoading([=] {
  461. QLOG_INFO() << "resuming loading";
  462. mpv::qt::command_variant(m_mpv, QStringList() << "hook-ack" << resumeId);
  463. });
  464. };
  465. if (switchDisplayFrameRate())
  466. {
  467. // Now wait for some time for mode change - this is needed because mode changing can take some
  468. // time, during which the screen is black, and initializing hardware decoding could fail due
  469. // to various strange OS-related reasons.
  470. // (Better hope the user doesn't try to exit Konvergo during mode change.)
  471. int pause = SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, "refreshrate.delay").toInt() * 1000;
  472. QLOG_INFO() << "waiting" << pause << "msec after rate switch before loading";
  473. QTimer::singleShot(pause, resume);
  474. }
  475. else
  476. {
  477. resume();
  478. }
  479. break;
  480. }
  481. // Start "on_preload" hook.
  482. // Used to probe codecs.
  483. if (!strcmp(msg->args[1], "2"))
  484. {
  485. startCodecsLoading([=] {
  486. mpv::qt::command_variant(m_mpv, QStringList() << "hook-ack" << resumeId);
  487. });
  488. break;
  489. }
  490. break;
  491. }
  492. default:; /* ignore */
  493. }
  494. }
  495. ///////////////////////////////////////////////////////////////////////////////////////////////////
  496. void PlayerComponent::handleMpvEvents()
  497. {
  498. // Process all events, until the event queue is empty.
  499. while (1)
  500. {
  501. mpv_event *event = mpv_wait_event(m_mpv, 0);
  502. if (event->event_id == MPV_EVENT_NONE)
  503. break;
  504. handleMpvEvent(event);
  505. }
  506. // Once we got all status updates, determine the new canonical state.
  507. updatePlaybackState();
  508. }
  509. ///////////////////////////////////////////////////////////////////////////////////////////////////
  510. void PlayerComponent::setVideoOnlyMode(bool enable)
  511. {
  512. if (m_window)
  513. {
  514. QQuickItem *web = m_window->findChild<QQuickItem *>("web");
  515. if (web)
  516. web->setVisible(!enable);
  517. }
  518. }
  519. ///////////////////////////////////////////////////////////////////////////////////////////////////
  520. void PlayerComponent::play()
  521. {
  522. QStringList args = (QStringList() << "set" << "pause" << "no");
  523. mpv::qt::command_variant(m_mpv, args);
  524. }
  525. ///////////////////////////////////////////////////////////////////////////////////////////////////
  526. void PlayerComponent::stop()
  527. {
  528. QStringList args("stop");
  529. mpv::qt::command_variant(m_mpv, args);
  530. }
  531. ///////////////////////////////////////////////////////////////////////////////////////////////////
  532. void PlayerComponent::clearQueue()
  533. {
  534. QStringList args("playlist_clear");
  535. mpv::qt::command_variant(m_mpv, args);
  536. }
  537. ///////////////////////////////////////////////////////////////////////////////////////////////////
  538. void PlayerComponent::pause()
  539. {
  540. QStringList args = (QStringList() << "set" << "pause" << "yes");
  541. mpv::qt::command_variant(m_mpv, args);
  542. }
  543. ///////////////////////////////////////////////////////////////////////////////////////////////////
  544. void PlayerComponent::seekTo(qint64 ms)
  545. {
  546. double timeSecs = ms / 1000.0;
  547. QVariantList args = (QVariantList() << "seek" << timeSecs << "absolute+exact");
  548. mpv::qt::command_variant(m_mpv, args);
  549. }
  550. ///////////////////////////////////////////////////////////////////////////////////////////////////
  551. QVariant PlayerComponent::getAudioDeviceList()
  552. {
  553. return mpv::qt::get_property_variant(m_mpv, "audio-device-list");
  554. }
  555. ///////////////////////////////////////////////////////////////////////////////////////////////////
  556. void PlayerComponent::setAudioDevice(const QString& name)
  557. {
  558. mpv::qt::set_property_variant(m_mpv, "audio-device", name);
  559. }
  560. ///////////////////////////////////////////////////////////////////////////////////////////////////
  561. void PlayerComponent::setVolume(int volume)
  562. {
  563. // Will fail if no audio output opened (i.e. no file playing)
  564. mpv::qt::set_property_variant(m_mpv, "volume", volume);
  565. }
  566. ///////////////////////////////////////////////////////////////////////////////////////////////////
  567. int PlayerComponent::volume()
  568. {
  569. QVariant volume = mpv::qt::get_property_variant(m_mpv, "volume");
  570. if (volume.isValid())
  571. return volume.toInt();
  572. return 0;
  573. }
  574. ///////////////////////////////////////////////////////////////////////////////////////////////////
  575. void PlayerComponent::setMuted(bool muted)
  576. {
  577. // Will fail if no audio output opened (i.e. no file playing)
  578. mpv::qt::set_property_variant(m_mpv, "mute", muted);
  579. }
  580. ///////////////////////////////////////////////////////////////////////////////////////////////////
  581. bool PlayerComponent::muted()
  582. {
  583. QVariant mute = mpv::qt::get_property_variant(m_mpv, "mute");
  584. if (mute.isValid())
  585. return mute.toBool();
  586. return false;
  587. }
  588. ///////////////////////////////////////////////////////////////////////////////////////////////////
  589. void PlayerComponent::setSubtitleStream(const QString &subtitleStream)
  590. {
  591. if (subtitleStream.isEmpty())
  592. {
  593. mpv::qt::set_property_variant(m_mpv, "ff-sid", "no");
  594. }
  595. else if (subtitleStream.startsWith("#"))
  596. {
  597. mpv::qt::set_property_variant(m_mpv, "ff-sid", subtitleStream.mid(1));
  598. }
  599. else
  600. {
  601. QStringList args = (QStringList() << "sub-add" << subtitleStream << "cached");
  602. mpv::qt::command_variant(m_mpv, args);
  603. }
  604. }
  605. ///////////////////////////////////////////////////////////////////////////////////////////////////
  606. void PlayerComponent::setAudioStream(const QString &audioStream)
  607. {
  608. if (audioStream.isEmpty())
  609. mpv::qt::set_property_variant(m_mpv, "ff-aid", "no");
  610. else
  611. mpv::qt::set_property_variant(m_mpv, "ff-aid", audioStream);
  612. }
  613. /////////////////////////////////////////////////////////////////////////////////////////
  614. void PlayerComponent::setAudioDelay(qint64 milliseconds)
  615. {
  616. m_playbackAudioDelay = milliseconds;
  617. double displayFps = DisplayComponent::Get().currentRefreshRate();
  618. const char *audioDelaySetting = "audio_delay.normal";
  619. if (fabs(displayFps - 24) < 1) // cover 24Hz, 23.976Hz, and values very close
  620. audioDelaySetting = "audio_delay.24hz";
  621. double fixedDelay = SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, audioDelaySetting).toFloat();
  622. mpv::qt::set_option_variant(m_mpv, "audio-delay", (fixedDelay + m_playbackAudioDelay) / 1000.0);
  623. }
  624. /////////////////////////////////////////////////////////////////////////////////////////
  625. void PlayerComponent::setSubtitleDelay(qint64 milliseconds)
  626. {
  627. mpv::qt::set_option_variant(m_mpv, "sub-delay", milliseconds / 1000.0);
  628. }
  629. ///////////////////////////////////////////////////////////////////////////////////////////////////
  630. void PlayerComponent::onReloadAudio()
  631. {
  632. mpv::qt::command_variant(m_mpv, QStringList() << "ao-reload");
  633. }
  634. ///////////////////////////////////////////////////////////////////////////////////////////////////
  635. // This is called with the set of previous audio devices that were detected, and the set of current
  636. // audio devices. From this we guess whether we should reopen the audio device. If the user-selected
  637. // device went away previously, and now comes back, reinitializing the player's audio output will
  638. // force the player and/or the OS to move audio output back to the user-selected device.
  639. void PlayerComponent::checkCurrentAudioDevice(const QSet<QString>& old_devs, const QSet<QString>& new_devs)
  640. {
  641. QString userDevice = SettingsComponent::Get().value(SETTINGS_SECTION_AUDIO, "device").toString();
  642. QSet<QString> removed = old_devs - new_devs;
  643. QSet<QString> added = new_devs - old_devs;
  644. QLOG_DEBUG() << "Audio devices removed:" << removed;
  645. QLOG_DEBUG() << "Audio devices added:" << added;
  646. QLOG_DEBUG() << "Audio device selected:" << userDevice;
  647. if (!mpv::qt::get_property_variant(m_mpv, "idle").toBool() && userDevice.length())
  648. {
  649. if (added.contains(userDevice))
  650. {
  651. // The timer is for debouncing the reload. Several change notifications could
  652. // come in quick succession. Also, it's possible that trying to open the
  653. // reappeared audio device immediately can fail.
  654. m_reloadAudioTimer.start(500);
  655. }
  656. }
  657. }
  658. ///////////////////////////////////////////////////////////////////////////////////////////////////
  659. void PlayerComponent::updateAudioDeviceList()
  660. {
  661. QVariantList settingList;
  662. QVariant list = getAudioDeviceList();
  663. QSet<QString> devices;
  664. for(const QVariant& d : list.toList())
  665. {
  666. Q_ASSERT(d.type() == QVariant::Map);
  667. QVariantMap dmap = d.toMap();
  668. devices.insert(dmap["name"].toString());
  669. QVariantMap entry;
  670. entry["value"] = dmap["name"];
  671. entry["title"] = dmap["description"];
  672. settingList << entry;
  673. }
  674. SettingsComponent::Get().updatePossibleValues(SETTINGS_SECTION_AUDIO, "device", settingList);
  675. checkCurrentAudioDevice(m_audioDevices, devices);
  676. m_audioDevices = devices;
  677. }
  678. ///////////////////////////////////////////////////////////////////////////////////////////////////
  679. void PlayerComponent::setAudioConfiguration()
  680. {
  681. QStringList aoDefaults;
  682. QString deviceType = SettingsComponent::Get().value(SETTINGS_SECTION_AUDIO, "devicetype").toString();
  683. if (SettingsComponent::Get().value(SETTINGS_SECTION_AUDIO, "exclusive").toBool())
  684. {
  685. aoDefaults << "wasapi:exclusive=yes";
  686. aoDefaults << "coreaudio:exclusive=yes";
  687. }
  688. mpv::qt::set_option_variant(m_mpv, "ao-defaults", aoDefaults.join(','));
  689. // set the audio device
  690. QVariant device = SettingsComponent::Get().value(SETTINGS_SECTION_AUDIO, "device");
  691. mpv::qt::set_property_variant(m_mpv, "audio-device", device);
  692. QString resampleOpts = "";
  693. bool normalize = SettingsComponent::Get().value(SETTINGS_SECTION_AUDIO, "normalize").toBool();
  694. resampleOpts += QString(":normalize=") + (normalize ? "yes" : "no");
  695. // Make downmix more similar to PHT.
  696. resampleOpts += ":o=[surround_mix_level=1]";
  697. mpv::qt::set_option_variant(m_mpv, "af-defaults", "lavrresample" + resampleOpts);
  698. m_passthroughCodecs.clear();
  699. // passthrough doesn't make sense with basic type
  700. if (deviceType != AUDIO_DEVICE_TYPE_BASIC)
  701. {
  702. SettingsSection* audioSection = SettingsComponent::Get().getSection(SETTINGS_SECTION_AUDIO);
  703. QStringList codecs;
  704. if (deviceType == AUDIO_DEVICE_TYPE_SPDIF)
  705. codecs = AudioCodecsSPDIF();
  706. else if (deviceType == AUDIO_DEVICE_TYPE_HDMI && audioSection->value("advanced").toBool())
  707. codecs = AudioCodecsAll();
  708. for(const QString& key : codecs)
  709. {
  710. if (audioSection->value("passthrough." + key).toBool())
  711. m_passthroughCodecs << key;
  712. }
  713. // dts-hd includes dts, but listing dts before dts-hd may disable dts-hd.
  714. if (m_passthroughCodecs.indexOf("dts-hd") != -1)
  715. m_passthroughCodecs.removeAll("dts");
  716. }
  717. QString passthroughCodecs = m_passthroughCodecs.join(",");
  718. mpv::qt::set_option_variant(m_mpv, "audio-spdif", passthroughCodecs);
  719. // set the channel layout
  720. QVariant layout = SettingsComponent::Get().value(SETTINGS_SECTION_AUDIO, "channels");
  721. // always force either stereo or transcoding
  722. if (deviceType == AUDIO_DEVICE_TYPE_SPDIF)
  723. layout = "2.0";
  724. mpv::qt::set_option_variant(m_mpv, "audio-channels", layout);
  725. // if the user has indicated that PCM only works for stereo, and that
  726. // the receiver supports AC3, set this extra option that allows us to transcode
  727. // 5.1 audio into a usable format, note that we only support AC3
  728. // here for now. We might need to add support for DTS transcoding
  729. // if we see user requests for it.
  730. //
  731. m_doAc3Transcoding = false;
  732. if (layout == "2.0" &&
  733. SettingsComponent::Get().value(SETTINGS_SECTION_AUDIO, "passthrough.ac3").toBool())
  734. {
  735. mpv::qt::command_variant(m_mpv, QStringList() << "af" << "add" << "@ac3:lavcac3enc");
  736. m_doAc3Transcoding = true;
  737. }
  738. else
  739. {
  740. mpv::qt::command_variant(m_mpv, QStringList() << "af" << "del" << "@ac3");
  741. }
  742. // Make a informational log message.
  743. QString audioConfig = QString(QString("Audio Config - device: %1, ") +
  744. "channel layout: %2, " +
  745. "passthrough codecs: %3, " +
  746. "ac3 transcoding: %4").arg(device.toString(),
  747. layout.toString(),
  748. passthroughCodecs.isEmpty() ? "none" : passthroughCodecs,
  749. m_doAc3Transcoding ? "yes" : "no");
  750. QLOG_INFO() << qPrintable(audioConfig);
  751. }
  752. ///////////////////////////////////////////////////////////////////////////////////////////////////
  753. void PlayerComponent::updateSubtitleSettings()
  754. {
  755. QVariant size = SettingsComponent::Get().value(SETTINGS_SECTION_SUBTITLES, "size");
  756. mpv::qt::set_option_variant(m_mpv, "sub-text-font-size", size);
  757. QVariant colorsString = SettingsComponent::Get().value(SETTINGS_SECTION_SUBTITLES, "color");
  758. auto colors = colorsString.toString().split(",");
  759. if (colors.length() == 2)
  760. {
  761. mpv::qt::set_option_variant(m_mpv, "sub-text-color", colors[0]);
  762. mpv::qt::set_option_variant(m_mpv, "sub-text-border-color", colors[1]);
  763. }
  764. QVariant subposString = SettingsComponent::Get().value(SETTINGS_SECTION_SUBTITLES, "placement");
  765. auto subpos = subposString.toString().split(",");
  766. if (subpos.length() == 2)
  767. {
  768. mpv::qt::set_option_variant(m_mpv, "sub-text-align-x", subpos[0]);
  769. mpv::qt::set_option_variant(m_mpv, "sub-text-align-y", subpos[1]);
  770. }
  771. }
  772. ///////////////////////////////////////////////////////////////////////////////////////////////////
  773. void PlayerComponent::updateVideoAspectSettings()
  774. {
  775. QVariant mode = SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, "aspect").toString();
  776. bool disableScaling = false;
  777. bool keepAspect = true;
  778. QString forceAspect = "-1";
  779. double panScan = 0.0;
  780. if (mode == "custom")
  781. {
  782. // in particular, do not restore anything - the intention is not to touch the user's mpv.conf settings, or whatever
  783. return;
  784. }
  785. else if (mode == "zoom")
  786. {
  787. panScan = 1.0;
  788. }
  789. else if (mode == "force_4_3")
  790. {
  791. forceAspect = "4:3";
  792. }
  793. else if (mode == "force_16_9")
  794. {
  795. forceAspect = "16:9";
  796. }
  797. else if (mode == "stretch")
  798. {
  799. keepAspect = false;
  800. }
  801. else if (mode == "noscaling")
  802. {
  803. disableScaling = true;
  804. }
  805. mpv::qt::set_property_variant(m_mpv, "video-unscaled", disableScaling);
  806. mpv::qt::set_property_variant(m_mpv, "video-aspect", forceAspect);
  807. mpv::qt::set_option_variant(m_mpv, "keepaspect", keepAspect);
  808. mpv::qt::set_property_variant(m_mpv, "panscan", panScan);
  809. }
  810. ///////////////////////////////////////////////////////////////////////////////////////////////////
  811. void PlayerComponent::updateVideoSettings()
  812. {
  813. QVariant syncMode = SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, "sync_mode");
  814. mpv::qt::set_option_variant(m_mpv, "video-sync", syncMode);
  815. QString hardwareDecodingMode = SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, "hardwareDecoding").toString();
  816. bool hwdecEnabled = false;
  817. QString hwdecVTFormat = "nv12";
  818. if (hardwareDecodingMode == "enabled") {
  819. hwdecEnabled = true;
  820. } else if (hardwareDecodingMode == "osx_compat") {
  821. hwdecEnabled = true;
  822. hwdecVTFormat = "uyvy422";
  823. }
  824. mpv::qt::set_property_variant(m_mpv, "hwdec", hwdecEnabled ? "auto" : "no");
  825. mpv::qt::set_option_variant(m_mpv, "videotoolbox-format", hwdecVTFormat);
  826. QVariant deinterlace = SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, "deinterlace");
  827. mpv::qt::set_option_variant(m_mpv, "deinterlace", deinterlace.toBool() ? "yes" : "no");
  828. #ifndef TARGET_RPI
  829. double displayFps = DisplayComponent::Get().currentRefreshRate();
  830. mpv::qt::set_property_variant(m_mpv, "display-fps", displayFps);
  831. #endif
  832. setAudioDelay(m_playbackAudioDelay);
  833. QVariant cache = SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, "cache");
  834. mpv::qt::set_option_variant(m_mpv, "cache", cache.toInt() * 1024);
  835. updateVideoAspectSettings();
  836. }
  837. /////////////////////////////////////////////////////////////////////////////////////////
  838. void PlayerComponent::userCommand(QString command)
  839. {
  840. QByteArray cmdUtf8 = command.toUtf8();
  841. mpv_command_string(m_mpv, cmdUtf8.data());
  842. }
  843. /////////////////////////////////////////////////////////////////////////////////////////
  844. void PlayerComponent::initializeCodecSupport()
  845. {
  846. QMap<QString, QString> all = { {"vc1", "WVC1"}, {"mpeg2video", "MPG2"} };
  847. for (auto name : all.keys())
  848. {
  849. bool ok = true;
  850. #ifdef TARGET_RPI
  851. char res[100] = "";
  852. bcm_host_init();
  853. if (vc_gencmd(res, sizeof(res), "codec_enabled %s", all[name].toUtf8().data()))
  854. res[0] = '\0'; // error
  855. ok = !!strstr(res, "=enabled");
  856. #endif
  857. m_codecSupport[name] = ok;
  858. QLOG_INFO() << "Codec" << name << (ok ? "present" : "disabled");
  859. }
  860. }
  861. /////////////////////////////////////////////////////////////////////////////////////////
  862. bool PlayerComponent::checkCodecSupport(const QString& codec)
  863. {
  864. if (m_codecSupport.contains(codec))
  865. return m_codecSupport[codec];
  866. return true; // doesn't matter if unknown codecs are reported as "ok"
  867. }
  868. /////////////////////////////////////////////////////////////////////////////////////////
  869. QList<CodecDriver> convertCodecList(QVariant list, CodecType type)
  870. {
  871. QList<CodecDriver> codecs;
  872. foreach (const QVariant& e, list.toList())
  873. {
  874. QVariantMap map = e.toMap();
  875. QString family = map["family"].toString();
  876. QString codec = map["codec"].toString();
  877. QString driver = map["driver"].toString();
  878. // Only include FFmpeg codecs; exclude pseudo-codecs like spdif.
  879. if (family != "lavc")
  880. continue;
  881. CodecDriver ncodec = {};
  882. ncodec.type = type;
  883. ncodec.format = codec;
  884. ncodec.driver = driver;
  885. ncodec.present = true;
  886. codecs.append(ncodec);
  887. }
  888. return codecs;
  889. }
  890. /////////////////////////////////////////////////////////////////////////////////////////
  891. QList<CodecDriver> PlayerComponent::installedCodecDrivers()
  892. {
  893. QList<CodecDriver> codecs;
  894. codecs.append(convertCodecList(mpv::qt::get_property_variant(m_mpv, "decoder-list"), CodecType::Decoder));
  895. codecs.append(convertCodecList(mpv::qt::get_property_variant(m_mpv, "encoder-list"), CodecType::Encoder));
  896. return codecs;
  897. }
  898. /////////////////////////////////////////////////////////////////////////////////////////
  899. QStringList PlayerComponent::installedDecoderCodecs()
  900. {
  901. QStringList formats;
  902. bool hasPcm = false;
  903. for (auto driver : installedCodecDrivers())
  904. {
  905. if (driver.type == CodecType::Decoder && checkCodecSupport(driver.format))
  906. {
  907. QString name = Codecs::plexNameFromFF(driver.format);
  908. if (name.startsWith("pcm_") && name != "pcm_bluray" && name != "pcm_dvd")
  909. {
  910. if (hasPcm)
  911. continue;
  912. hasPcm = true;
  913. name = "pcm";
  914. }
  915. formats.append(name);
  916. }
  917. }
  918. return formats;
  919. }
  920. /////////////////////////////////////////////////////////////////////////////////////////
  921. PlaybackInfo PlayerComponent::getPlaybackInfo()
  922. {
  923. PlaybackInfo info = {};
  924. for (auto codec : m_passthroughCodecs)
  925. {
  926. // Normalize back to canonical codec names.
  927. if (codec == "dts-hd")
  928. codec = "dts";
  929. info.audioPassthroughCodecs.insert(codec);
  930. }
  931. info.enableAC3Transcoding = m_doAc3Transcoding;
  932. auto tracks = mpv::qt::get_property_variant(m_mpv, "track-list");
  933. for (auto track : tracks.toList())
  934. {
  935. QVariantMap map = track.toMap();
  936. QString type = map["type"].toString();
  937. StreamInfo stream = {};
  938. stream.isVideo = type == "video";
  939. stream.isAudio = type == "audio";
  940. stream.codec = map["codec"].toString();
  941. stream.audioChannels = map["demux-channel-count"].toInt();
  942. stream.videoResolution = QSize(map["demux-w"].toInt(), map["demux-h"].toInt());
  943. // Get the profile from the server, because mpv can't determine it yet.
  944. if (stream.isVideo)
  945. {
  946. int index = map["ff-index"].toInt();
  947. for (auto partInfo : m_serverMediaInfo["Part"].toList())
  948. {
  949. for (auto streamInfo : partInfo.toMap()["Stream"].toList())
  950. {
  951. auto streamInfoMap = streamInfo.toMap();
  952. bool ok = false;
  953. if (streamInfoMap["index"].toInt(&ok) == index && ok)
  954. stream.profile = streamInfoMap["profile"].toString();
  955. }
  956. }
  957. }
  958. info.streams.append(stream);
  959. }
  960. // If we're in an early stage where we don't have streams yet, try to get the
  961. // info from the PMS metadata.
  962. if (!info.streams.size())
  963. {
  964. for (auto partInfo : m_serverMediaInfo["Part"].toList())
  965. {
  966. for (auto streamInfo : partInfo.toMap()["Stream"].toList())
  967. {
  968. auto streamInfoMap = streamInfo.toMap();
  969. StreamInfo stream = {};
  970. stream.isVideo = streamInfoMap["width"].isValid();
  971. stream.isAudio = streamInfoMap["channels"].isValid();
  972. stream.codec = Codecs::plexNameToFF(streamInfoMap["codec"].toString());
  973. stream.audioChannels = streamInfoMap["channels"].toInt();
  974. stream.videoResolution = QSize(streamInfoMap["width"].toInt(), streamInfoMap["height"].toInt());
  975. stream.profile = streamInfoMap["profile"].toString();
  976. info.streams.append(stream);
  977. }
  978. }
  979. }
  980. return info;
  981. }
  982. /////////////////////////////////////////////////////////////////////////////////////////
  983. void PlayerComponent::setPreferredCodecs(const QList<CodecDriver>& codecs)
  984. {
  985. QStringList items;
  986. for (auto codec : codecs)
  987. {
  988. if (codec.type == CodecType::Decoder)
  989. {
  990. items << QString("lavc:") + codec.driver;
  991. }
  992. }
  993. QString opt = items.join(",");
  994. // For simplicity, we don't distinguish between audio and video. The player
  995. // will ignore entries with mismatching media type.
  996. mpv::qt::set_option_variant(m_mpv, "ad", opt);
  997. mpv::qt::set_option_variant(m_mpv, "vd", opt);
  998. }
  999. // For QVariant.
  1000. Q_DECLARE_METATYPE(std::function<void()>);
  1001. /////////////////////////////////////////////////////////////////////////////////////////
  1002. void PlayerComponent::startCodecsLoading(std::function<void()> resume)
  1003. {
  1004. auto fetcher = new CodecsFetcher();
  1005. fetcher->userData = QVariant::fromValue(resume);
  1006. connect(fetcher, &CodecsFetcher::done, this, &PlayerComponent::onCodecsLoadingDone);
  1007. Codecs::updateCachedCodecList();
  1008. QList<CodecDriver> codecs = Codecs::determineRequiredCodecs(getPlaybackInfo());
  1009. setPreferredCodecs(codecs);
  1010. fetcher->installCodecs(codecs);
  1011. }
  1012. /////////////////////////////////////////////////////////////////////////////////////////
  1013. void PlayerComponent::onCodecsLoadingDone(CodecsFetcher* sender)
  1014. {
  1015. sender->deleteLater();
  1016. sender->userData.value<std::function<void()>>()();
  1017. }
  1018. /////////////////////////////////////////////////////////////////////////////////////////
  1019. static QString get_mpv_osd(mpv_handle *ctx, const QString& property)
  1020. {
  1021. char *s = mpv_get_property_osd_string(ctx, property.toUtf8().data());
  1022. if (!s)
  1023. return "-";
  1024. QString r = QString::fromUtf8(s);
  1025. mpv_free(s);
  1026. Log::CensorAuthTokens(r);
  1027. return r;
  1028. }
  1029. #define MPV_PROPERTY(p) get_mpv_osd(m_mpv, p)
  1030. #define MPV_PROPERTY_BOOL(p) (mpv::qt::get_property_variant(m_mpv, p).toBool())
  1031. /////////////////////////////////////////////////////////////////////////////////////////
  1032. void PlayerComponent::appendAudioFormat(QTextStream& info, const QString& property) const
  1033. {
  1034. // Guess if it's a passthrough format. Don't show the channel layout in this
  1035. // case, because it's confusing.
  1036. QString audioFormat = MPV_PROPERTY(property + "/format");
  1037. if (audioFormat.startsWith("spdif-"))
  1038. {
  1039. info << "passthrough (" << audioFormat.mid(6) << ")";
  1040. }
  1041. else
  1042. {
  1043. QString hr = MPV_PROPERTY(property + "/hr-channels");
  1044. QString full = MPV_PROPERTY(property + "/channels");
  1045. info << hr;
  1046. if (hr != full)
  1047. info << " (" << full << ")";
  1048. }
  1049. }
  1050. /////////////////////////////////////////////////////////////////////////////////////////
  1051. QString PlayerComponent::videoInformation() const
  1052. {
  1053. QString infoStr;
  1054. QTextStream info(&infoStr);
  1055. // check if video is playing
  1056. if (mpv::qt::get_property_variant(m_mpv, "idle").toBool())
  1057. return "";
  1058. info << "File:" << endl;
  1059. info << "URL: " << MPV_PROPERTY("path") << endl;
  1060. info << "Container: " << MPV_PROPERTY("file-format") << endl;
  1061. info << "Native seeking: " << ((MPV_PROPERTY_BOOL("seekable") &&
  1062. !MPV_PROPERTY_BOOL("partially-seekable"))
  1063. ? "yes" : "no") << endl;
  1064. info << endl;
  1065. info << "Video:" << endl;
  1066. info << "Codec: " << MPV_PROPERTY("video-codec") << endl;
  1067. info << "Size: " << MPV_PROPERTY("video-params/dw") << "x"
  1068. << MPV_PROPERTY("video-params/dh") << endl;
  1069. info << "FPS (container): " << MPV_PROPERTY("fps") << endl;
  1070. info << "FPS (filters): " << MPV_PROPERTY("estimated-vf-fps") << endl;
  1071. info << "Aspect: " << MPV_PROPERTY("video-aspect") << endl;
  1072. info << "Bitrate: " << MPV_PROPERTY("video-bitrate") << endl;
  1073. double displayFps = DisplayComponent::Get().currentRefreshRate();
  1074. info << "Display FPS: " << MPV_PROPERTY("display-fps")
  1075. << " (" << displayFps << ")" << endl;
  1076. info << "Hardware Decoding: " << MPV_PROPERTY("hwdec-current")
  1077. << " (" << MPV_PROPERTY("hwdec-interop") << ")" << endl;
  1078. info << endl;
  1079. info << "Audio: " << endl;
  1080. info << "Codec: " << MPV_PROPERTY("audio-codec") << endl;
  1081. info << "Bitrate: " << MPV_PROPERTY("audio-bitrate") << endl;
  1082. info << "Channels: ";
  1083. appendAudioFormat(info, "audio-params");
  1084. info << " -> ";
  1085. appendAudioFormat(info, "audio-out-params");
  1086. info << endl;
  1087. info << "Output driver: " << MPV_PROPERTY("current-ao") << endl;
  1088. info << endl;
  1089. info << "Performance: " << endl;
  1090. info << "A/V: " << MPV_PROPERTY("avsync") << endl;
  1091. info << "Dropped frames: " << MPV_PROPERTY("vo-drop-frame-count") << endl;
  1092. bool dispSync = MPV_PROPERTY_BOOL("display-sync-active");
  1093. info << "Display Sync: ";
  1094. if (!dispSync)
  1095. {
  1096. info << "no" << endl;
  1097. }
  1098. else
  1099. {
  1100. info << "yes (ratio " << MPV_PROPERTY("vsync-ratio") << ")" << endl;
  1101. info << "Mistimed frames: " << MPV_PROPERTY("mistimed-frame-count")
  1102. << "/" << MPV_PROPERTY("vo-delayed-frame-count") << endl;
  1103. info << "Measured FPS: " << MPV_PROPERTY("estimated-display-fps")
  1104. << " (" << MPV_PROPERTY("vsync-jitter") << ")" << endl;
  1105. info << "V. speed corr.: " << MPV_PROPERTY("video-speed-correction") << endl;
  1106. info << "A. speed corr.: " << MPV_PROPERTY("audio-speed-correction") << endl;
  1107. }
  1108. info << endl;
  1109. info << "Cache:" << endl;
  1110. info << "Seconds: " << MPV_PROPERTY("demuxer-cache-duration") << endl;
  1111. info << "Extra readahead: " << MPV_PROPERTY("cache-used") << endl;
  1112. info << "Buffering: " << MPV_PROPERTY("cache-buffering-state") << endl;
  1113. info << "Speed: " << MPV_PROPERTY("cache-speed") << endl;
  1114. info << endl;
  1115. info << "Misc: " << endl;
  1116. info << "Time: " << MPV_PROPERTY("playback-time") << " / "
  1117. << MPV_PROPERTY("duration")
  1118. << " (" << MPV_PROPERTY("percent-pos") << "%)" << endl;
  1119. info << "State: " << (MPV_PROPERTY_BOOL("pause") ? "paused " : "")
  1120. << (MPV_PROPERTY_BOOL("paused-for-cache") ? "buffering " : "")
  1121. << (MPV_PROPERTY_BOOL("core-idle") ? "waiting " : "playing ")
  1122. << (MPV_PROPERTY_BOOL("seeking") ? "seeking " : "")
  1123. << endl;
  1124. info << flush;
  1125. return infoStr;
  1126. }