media-controls.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. #include "window-basic-main.hpp"
  2. #include "moc_media-controls.cpp"
  3. #include "obs-app.hpp"
  4. #include <QToolTip>
  5. #include <QStyle>
  6. #include <QMenu>
  7. #include "ui_media-controls.h"
  8. void MediaControls::OBSMediaStopped(void *data, calldata_t *)
  9. {
  10. MediaControls *media = static_cast<MediaControls *>(data);
  11. QMetaObject::invokeMethod(media, "SetRestartState");
  12. }
  13. void MediaControls::OBSMediaPlay(void *data, calldata_t *)
  14. {
  15. MediaControls *media = static_cast<MediaControls *>(data);
  16. QMetaObject::invokeMethod(media, "SetPlayingState");
  17. }
  18. void MediaControls::OBSMediaPause(void *data, calldata_t *)
  19. {
  20. MediaControls *media = static_cast<MediaControls *>(data);
  21. QMetaObject::invokeMethod(media, "SetPausedState");
  22. }
  23. void MediaControls::OBSMediaStarted(void *data, calldata_t *)
  24. {
  25. MediaControls *media = static_cast<MediaControls *>(data);
  26. QMetaObject::invokeMethod(media, "SetPlayingState");
  27. }
  28. void MediaControls::OBSMediaNext(void *data, calldata_t *)
  29. {
  30. MediaControls *media = static_cast<MediaControls *>(data);
  31. QMetaObject::invokeMethod(media, "UpdateSlideCounter");
  32. }
  33. void MediaControls::OBSMediaPrevious(void *data, calldata_t *)
  34. {
  35. MediaControls *media = static_cast<MediaControls *>(data);
  36. QMetaObject::invokeMethod(media, "UpdateSlideCounter");
  37. }
  38. MediaControls::MediaControls(QWidget *parent)
  39. : QWidget(parent),
  40. ui(new Ui::MediaControls)
  41. {
  42. ui->setupUi(this);
  43. ui->playPauseButton->setProperty("class", "icon-media-play");
  44. ui->previousButton->setProperty("class", "icon-media-prev");
  45. ui->nextButton->setProperty("class", "icon-media-next");
  46. ui->stopButton->setProperty("class", "icon-media-stop");
  47. setFocusPolicy(Qt::StrongFocus);
  48. connect(&mediaTimer, &QTimer::timeout, this,
  49. &MediaControls::SetSliderPosition);
  50. connect(&seekTimer, &QTimer::timeout, this,
  51. &MediaControls::SeekTimerCallback);
  52. connect(ui->slider, &AbsoluteSlider::sliderPressed, this,
  53. &MediaControls::AbsoluteSliderClicked);
  54. connect(ui->slider, &AbsoluteSlider::absoluteSliderHovered, this,
  55. &MediaControls::AbsoluteSliderHovered);
  56. connect(ui->slider, &AbsoluteSlider::sliderReleased, this,
  57. &MediaControls::AbsoluteSliderReleased);
  58. connect(ui->slider, &AbsoluteSlider::sliderMoved, this,
  59. &MediaControls::AbsoluteSliderMoved);
  60. countDownTimer = config_get_bool(App()->GetUserConfig(), "BasicWindow",
  61. "MediaControlsCountdownTimer");
  62. QAction *restartAction = new QAction(this);
  63. restartAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
  64. restartAction->setShortcut({Qt::Key_R});
  65. connect(restartAction, &QAction::triggered, this,
  66. &MediaControls::RestartMedia);
  67. addAction(restartAction);
  68. QAction *sliderFoward = new QAction(this);
  69. sliderFoward->setShortcutContext(Qt::WidgetWithChildrenShortcut);
  70. connect(sliderFoward, &QAction::triggered, this,
  71. &MediaControls::MoveSliderFoward);
  72. sliderFoward->setShortcut({Qt::Key_Right});
  73. addAction(sliderFoward);
  74. QAction *sliderBack = new QAction(this);
  75. sliderBack->setShortcutContext(Qt::WidgetWithChildrenShortcut);
  76. connect(sliderBack, &QAction::triggered, this,
  77. &MediaControls::MoveSliderBackwards);
  78. sliderBack->setShortcut({Qt::Key_Left});
  79. addAction(sliderBack);
  80. QAction *playPause = new QAction(this);
  81. playPause->setShortcutContext(Qt::WidgetWithChildrenShortcut);
  82. connect(playPause, &QAction::triggered, this,
  83. &MediaControls::on_playPauseButton_clicked);
  84. playPause->setShortcut({Qt::Key_Space});
  85. addAction(playPause);
  86. }
  87. MediaControls::~MediaControls() {}
  88. bool MediaControls::MediaPaused()
  89. {
  90. OBSSource source = OBSGetStrongRef(weakSource);
  91. if (!source) {
  92. return false;
  93. }
  94. obs_media_state state = obs_source_media_get_state(source);
  95. return state == OBS_MEDIA_STATE_PAUSED;
  96. }
  97. int64_t MediaControls::GetSliderTime(int val)
  98. {
  99. OBSSource source = OBSGetStrongRef(weakSource);
  100. if (!source) {
  101. return 0;
  102. }
  103. float percent = (float)val / (float)ui->slider->maximum();
  104. float duration = (float)obs_source_media_get_duration(source);
  105. int64_t seekTo = (int64_t)(percent * duration);
  106. return seekTo;
  107. }
  108. void MediaControls::AbsoluteSliderClicked()
  109. {
  110. OBSSource source = OBSGetStrongRef(weakSource);
  111. if (!source) {
  112. return;
  113. }
  114. obs_media_state state = obs_source_media_get_state(source);
  115. if (state == OBS_MEDIA_STATE_PAUSED) {
  116. prevPaused = true;
  117. } else if (state == OBS_MEDIA_STATE_PLAYING) {
  118. prevPaused = false;
  119. PauseMedia();
  120. StopMediaTimer();
  121. }
  122. seek = ui->slider->value();
  123. seekTimer.start(100);
  124. }
  125. void MediaControls::AbsoluteSliderReleased()
  126. {
  127. OBSSource source = OBSGetStrongRef(weakSource);
  128. if (!source) {
  129. return;
  130. }
  131. if (seekTimer.isActive()) {
  132. seekTimer.stop();
  133. if (lastSeek != seek) {
  134. obs_source_media_set_time(source, GetSliderTime(seek));
  135. }
  136. UpdateLabels(seek);
  137. seek = lastSeek = -1;
  138. }
  139. if (!prevPaused) {
  140. PlayMedia();
  141. StartMediaTimer();
  142. }
  143. }
  144. void MediaControls::AbsoluteSliderHovered(int val)
  145. {
  146. float seconds = ((float)GetSliderTime(val) / 1000.0f);
  147. QToolTip::showText(QCursor::pos(), FormatSeconds((int)seconds), this);
  148. }
  149. void MediaControls::AbsoluteSliderMoved(int val)
  150. {
  151. if (seekTimer.isActive()) {
  152. seek = val;
  153. UpdateLabels(seek);
  154. }
  155. }
  156. void MediaControls::SeekTimerCallback()
  157. {
  158. if (lastSeek != seek) {
  159. OBSSource source = OBSGetStrongRef(weakSource);
  160. if (source) {
  161. obs_source_media_set_time(source, GetSliderTime(seek));
  162. }
  163. lastSeek = seek;
  164. }
  165. }
  166. void MediaControls::StartMediaTimer()
  167. {
  168. if (isSlideshow)
  169. return;
  170. if (!mediaTimer.isActive())
  171. mediaTimer.start(1000);
  172. }
  173. void MediaControls::StopMediaTimer()
  174. {
  175. if (mediaTimer.isActive())
  176. mediaTimer.stop();
  177. }
  178. void MediaControls::SetPlayingState()
  179. {
  180. ui->slider->setEnabled(true);
  181. ui->playPauseButton->setProperty("class", "icon-media-pause");
  182. ui->playPauseButton->style()->unpolish(ui->playPauseButton);
  183. ui->playPauseButton->style()->polish(ui->playPauseButton);
  184. ui->playPauseButton->setToolTip(
  185. QTStr("ContextBar.MediaControls.PauseMedia"));
  186. prevPaused = false;
  187. UpdateSlideCounter();
  188. StartMediaTimer();
  189. }
  190. void MediaControls::SetPausedState()
  191. {
  192. ui->playPauseButton->setProperty("class", "icon-media-play");
  193. ui->playPauseButton->style()->unpolish(ui->playPauseButton);
  194. ui->playPauseButton->style()->polish(ui->playPauseButton);
  195. ui->playPauseButton->setToolTip(
  196. QTStr("ContextBar.MediaControls.PlayMedia"));
  197. StopMediaTimer();
  198. }
  199. void MediaControls::SetRestartState()
  200. {
  201. ui->playPauseButton->setProperty("class", "icon-media-restart");
  202. ui->playPauseButton->style()->unpolish(ui->playPauseButton);
  203. ui->playPauseButton->style()->polish(ui->playPauseButton);
  204. ui->playPauseButton->setToolTip(
  205. QTStr("ContextBar.MediaControls.RestartMedia"));
  206. ui->slider->setValue(0);
  207. if (!isSlideshow) {
  208. ui->timerLabel->setText("--:--:--");
  209. ui->durationLabel->setText("--:--:--");
  210. } else {
  211. ui->timerLabel->setText("-");
  212. ui->durationLabel->setText("-");
  213. }
  214. ui->slider->setEnabled(false);
  215. StopMediaTimer();
  216. }
  217. void MediaControls::RefreshControls()
  218. {
  219. OBSSource source;
  220. source = OBSGetStrongRef(weakSource);
  221. uint32_t flags = 0;
  222. const char *id = nullptr;
  223. if (source) {
  224. flags = obs_source_get_output_flags(source);
  225. id = obs_source_get_unversioned_id(source);
  226. }
  227. if (!source || !(flags & OBS_SOURCE_CONTROLLABLE_MEDIA)) {
  228. SetRestartState();
  229. setEnabled(false);
  230. hide();
  231. return;
  232. } else {
  233. setEnabled(true);
  234. show();
  235. }
  236. bool has_playlist = strcmp(id, "ffmpeg_source") != 0;
  237. ui->previousButton->setVisible(has_playlist);
  238. ui->nextButton->setVisible(has_playlist);
  239. isSlideshow = strcmp(id, "slideshow") == 0;
  240. ui->slider->setVisible(!isSlideshow);
  241. ui->emptySpaceAgain->setVisible(isSlideshow);
  242. obs_media_state state = obs_source_media_get_state(source);
  243. switch (state) {
  244. case OBS_MEDIA_STATE_STOPPED:
  245. case OBS_MEDIA_STATE_ENDED:
  246. case OBS_MEDIA_STATE_NONE:
  247. SetRestartState();
  248. break;
  249. case OBS_MEDIA_STATE_PLAYING:
  250. SetPlayingState();
  251. break;
  252. case OBS_MEDIA_STATE_PAUSED:
  253. SetPausedState();
  254. break;
  255. default:
  256. break;
  257. }
  258. if (isSlideshow)
  259. UpdateSlideCounter();
  260. else
  261. SetSliderPosition();
  262. }
  263. OBSSource MediaControls::GetSource()
  264. {
  265. return OBSGetStrongRef(weakSource);
  266. }
  267. void MediaControls::SetSource(OBSSource source)
  268. {
  269. sigs.clear();
  270. if (source) {
  271. weakSource = OBSGetWeakRef(source);
  272. signal_handler_t *sh = obs_source_get_signal_handler(source);
  273. sigs.emplace_back(sh, "media_play", OBSMediaPlay, this);
  274. sigs.emplace_back(sh, "media_pause", OBSMediaPause, this);
  275. sigs.emplace_back(sh, "media_restart", OBSMediaPlay, this);
  276. sigs.emplace_back(sh, "media_stopped", OBSMediaStopped, this);
  277. sigs.emplace_back(sh, "media_started", OBSMediaStarted, this);
  278. sigs.emplace_back(sh, "media_ended", OBSMediaStopped, this);
  279. sigs.emplace_back(sh, "media_next", OBSMediaNext, this);
  280. sigs.emplace_back(sh, "media_previous", OBSMediaPrevious, this);
  281. } else {
  282. weakSource = nullptr;
  283. }
  284. RefreshControls();
  285. }
  286. void MediaControls::SetSliderPosition()
  287. {
  288. OBSSource source = OBSGetStrongRef(weakSource);
  289. if (!source) {
  290. return;
  291. }
  292. float time = (float)obs_source_media_get_time(source);
  293. float duration = (float)obs_source_media_get_duration(source);
  294. float sliderPosition;
  295. if (duration)
  296. sliderPosition =
  297. (time / duration) * (float)ui->slider->maximum();
  298. else
  299. sliderPosition = 0.0f;
  300. ui->slider->setValue((int)sliderPosition);
  301. UpdateLabels((int)sliderPosition);
  302. }
  303. QString MediaControls::FormatSeconds(int totalSeconds)
  304. {
  305. int seconds = totalSeconds % 60;
  306. int totalMinutes = totalSeconds / 60;
  307. int minutes = totalMinutes % 60;
  308. int hours = totalMinutes / 60;
  309. return QString::asprintf("%02d:%02d:%02d", hours, minutes, seconds);
  310. }
  311. void MediaControls::on_playPauseButton_clicked()
  312. {
  313. OBSSource source = OBSGetStrongRef(weakSource);
  314. if (!source) {
  315. return;
  316. }
  317. obs_media_state state = obs_source_media_get_state(source);
  318. switch (state) {
  319. case OBS_MEDIA_STATE_STOPPED:
  320. case OBS_MEDIA_STATE_ENDED:
  321. RestartMedia();
  322. break;
  323. case OBS_MEDIA_STATE_PLAYING:
  324. PauseMedia();
  325. break;
  326. case OBS_MEDIA_STATE_PAUSED:
  327. PlayMedia();
  328. break;
  329. default:
  330. break;
  331. }
  332. }
  333. void MediaControls::RestartMedia()
  334. {
  335. OBSSource source = OBSGetStrongRef(weakSource);
  336. if (source) {
  337. obs_source_media_restart(source);
  338. }
  339. }
  340. void MediaControls::PlayMedia()
  341. {
  342. OBSSource source = OBSGetStrongRef(weakSource);
  343. if (source) {
  344. obs_source_media_play_pause(source, false);
  345. }
  346. }
  347. void MediaControls::PauseMedia()
  348. {
  349. OBSSource source = OBSGetStrongRef(weakSource);
  350. if (source) {
  351. obs_source_media_play_pause(source, true);
  352. }
  353. }
  354. void MediaControls::StopMedia()
  355. {
  356. OBSSource source = OBSGetStrongRef(weakSource);
  357. if (source) {
  358. obs_source_media_stop(source);
  359. }
  360. }
  361. void MediaControls::PlaylistNext()
  362. {
  363. OBSSource source = OBSGetStrongRef(weakSource);
  364. if (source) {
  365. obs_source_media_next(source);
  366. }
  367. }
  368. void MediaControls::PlaylistPrevious()
  369. {
  370. OBSSource source = OBSGetStrongRef(weakSource);
  371. if (source) {
  372. obs_source_media_previous(source);
  373. }
  374. }
  375. void MediaControls::on_stopButton_clicked()
  376. {
  377. StopMedia();
  378. }
  379. void MediaControls::on_nextButton_clicked()
  380. {
  381. PlaylistNext();
  382. }
  383. void MediaControls::on_previousButton_clicked()
  384. {
  385. PlaylistPrevious();
  386. }
  387. void MediaControls::on_durationLabel_clicked()
  388. {
  389. countDownTimer = !countDownTimer;
  390. config_set_bool(App()->GetUserConfig(), "BasicWindow",
  391. "MediaControlsCountdownTimer", countDownTimer);
  392. if (MediaPaused())
  393. SetSliderPosition();
  394. }
  395. void MediaControls::MoveSliderFoward(int seconds)
  396. {
  397. OBSSource source = OBSGetStrongRef(weakSource);
  398. if (!source)
  399. return;
  400. int ms = obs_source_media_get_time(source);
  401. ms += seconds * 1000;
  402. obs_source_media_set_time(source, ms);
  403. SetSliderPosition();
  404. }
  405. void MediaControls::MoveSliderBackwards(int seconds)
  406. {
  407. OBSSource source = OBSGetStrongRef(weakSource);
  408. if (!source)
  409. return;
  410. int ms = obs_source_media_get_time(source);
  411. ms -= seconds * 1000;
  412. obs_source_media_set_time(source, ms);
  413. SetSliderPosition();
  414. }
  415. void MediaControls::UpdateSlideCounter()
  416. {
  417. if (!isSlideshow)
  418. return;
  419. OBSSource source = OBSGetStrongRef(weakSource);
  420. if (!source)
  421. return;
  422. proc_handler_t *ph = obs_source_get_proc_handler(source);
  423. calldata_t cd = {};
  424. proc_handler_call(ph, "current_index", &cd);
  425. int slide = calldata_int(&cd, "current_index");
  426. proc_handler_call(ph, "total_files", &cd);
  427. int total = calldata_int(&cd, "total_files");
  428. calldata_free(&cd);
  429. if (total > 0) {
  430. ui->timerLabel->setText(QString::number(slide + 1));
  431. ui->durationLabel->setText(QString::number(total));
  432. } else {
  433. ui->timerLabel->setText("-");
  434. ui->durationLabel->setText("-");
  435. }
  436. }
  437. void MediaControls::UpdateLabels(int val)
  438. {
  439. OBSSource source = OBSGetStrongRef(weakSource);
  440. if (!source) {
  441. return;
  442. }
  443. float duration = (float)obs_source_media_get_duration(source);
  444. float percent = (float)val / (float)ui->slider->maximum();
  445. float time = percent * duration;
  446. ui->timerLabel->setText(FormatSeconds((int)(time / 1000.0f)));
  447. if (!countDownTimer)
  448. ui->durationLabel->setText(
  449. FormatSeconds((int)(duration / 1000.0f)));
  450. else
  451. ui->durationLabel->setText(
  452. QString("-") +
  453. FormatSeconds((int)((duration - time) / 1000.0f)));
  454. }