PlayerComponent.cpp 51 KB

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