media-controls.cpp 12 KB

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