media-controls.cpp 12 KB

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