PlayerComponent.cpp 50 KB

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