PlayerComponent.cpp 53 KB

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