index.vue 51 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158
  1. <template>
  2. <div :style="'--primary-color: var(--' + station.theme + ')'">
  3. <metadata v-if="exists && !loading" :title="`${station.displayName}`" />
  4. <metadata v-else-if="!exists && !loading" :title="`Not found`" />
  5. <div id="page-loader-container" v-if="loading">
  6. <content-loader
  7. width="1920"
  8. height="1080"
  9. :primary-color="nightmode ? '#222' : '#fff'"
  10. :secondary-color="nightmode ? '#444' : '#ddd'"
  11. preserve-aspect-ratio="none"
  12. id="page-loader-content"
  13. >
  14. <rect x="100" y="108" rx="5" ry="5" width="1048" height="672" />
  15. <rect x="100" y="810" rx="5" ry="5" width="1048" height="110" />
  16. <rect x="1190" y="110" rx="5" ry="5" width="630" height="149" />
  17. <rect x="1190" y="288" rx="5" ry="5" width="630" height="630" />
  18. </content-loader>
  19. <content-loader
  20. width="1920"
  21. height="1080"
  22. :primary-color="nightmode ? '#222' : '#fff'"
  23. :secondary-color="nightmode ? '#444' : '#ddd'"
  24. preserve-aspect-ratio="none"
  25. id="page-loader-layout"
  26. >
  27. <rect x="0" y="0" rx="0" ry="0" width="1920" height="64" />
  28. <rect x="0" y="980" rx="0" ry="0" width="1920" height="100" />
  29. </content-loader>
  30. </div>
  31. <!-- More simplistic loading animation for mobile users -->
  32. <div v-show="loading" id="mobile-progress-animation" />
  33. <div v-show="!loading">
  34. <main-header v-if="exists" />
  35. <div
  36. id="station-outer-container"
  37. :style="[!exists ? { margin: 0, padding: 0 } : {}]"
  38. >
  39. <div
  40. v-show="exists"
  41. id="station-inner-container"
  42. :class="{ 'nothing-here': noSong }"
  43. >
  44. <div id="station-left-column" class="column">
  45. <div class="player-container quadrant" v-show="!noSong">
  46. <div id="video-container">
  47. <div
  48. id="stationPlayer"
  49. style="
  50. width: 100%;
  51. height: 100%;
  52. min-height: 200px;
  53. "
  54. />
  55. <div
  56. class="player-cannot-autoplay"
  57. v-if="!canAutoplay"
  58. @click="
  59. increaseVolume() && decreaseVolume()
  60. "
  61. >
  62. <p>
  63. Please click anywhere on the screen for
  64. the video to start
  65. </p>
  66. </div>
  67. </div>
  68. <div id="seeker-bar-container">
  69. <div id="seeker-bar" style="width: 0%" />
  70. </div>
  71. <div id="control-bar-container">
  72. <div id="left-buttons">
  73. <!-- Debug Box -->
  74. <button
  75. class="button is-primary"
  76. @click="togglePlayerDebugBox()"
  77. @dblclick="resetPlayerDebugBox()"
  78. >
  79. <i
  80. class="material-icons icon-with-button"
  81. >
  82. bug_report
  83. </i>
  84. </button>
  85. <!-- Local Pause/Resume Button -->
  86. <button
  87. class="button is-primary"
  88. @click="resumeLocalStation()"
  89. id="local-resume"
  90. v-if="localPaused"
  91. >
  92. <i class="material-icons">play_arrow</i>
  93. </button>
  94. <button
  95. class="button is-primary"
  96. @click="pauseLocalStation()"
  97. id="local-pause"
  98. v-else
  99. >
  100. <i class="material-icons">pause</i>
  101. </button>
  102. <!-- Vote to Skip Button -->
  103. <button
  104. v-if="loggedIn"
  105. class="button is-primary"
  106. @click="voteSkipStation()"
  107. >
  108. <i
  109. class="material-icons icon-with-button"
  110. >skip_next</i
  111. >
  112. {{ currentSong.skipVotes }}
  113. </button>
  114. <button
  115. v-else
  116. class="button is-primary tooltip tooltip-top disabled"
  117. data-tooltip="Login to vote to skip songs"
  118. >
  119. <i
  120. class="material-icons icon-with-button"
  121. >skip_next</i
  122. >
  123. {{ currentSong.skipVotes }}
  124. </button>
  125. </div>
  126. <div id="duration">
  127. <p>
  128. {{ timeElapsed }} /
  129. {{
  130. utils.formatTime(
  131. currentSong.duration
  132. )
  133. }}
  134. </p>
  135. </div>
  136. <p id="volume-control">
  137. <i
  138. v-if="muted"
  139. class="material-icons"
  140. @click="toggleMute()"
  141. >volume_mute</i
  142. >
  143. <i
  144. v-else
  145. class="material-icons"
  146. @click="toggleMute()"
  147. >volume_down</i
  148. >
  149. <input
  150. v-model="volumeSliderValue"
  151. type="range"
  152. min="0"
  153. max="10000"
  154. class="volume-slider active"
  155. @change="changeVolume()"
  156. @input="changeVolume()"
  157. />
  158. <i
  159. class="material-icons"
  160. @click="increaseVolume()"
  161. >volume_up</i
  162. >
  163. </p>
  164. <div id="right-buttons" v-if="loggedIn">
  165. <!-- Ratings (Like/Dislike) Buttons -->
  166. <div
  167. id="ratings"
  168. v-if="
  169. currentSong.likes !== -1 &&
  170. currentSong.dislikes !== -1
  171. "
  172. :class="{
  173. liked: liked,
  174. disliked: disliked
  175. }"
  176. >
  177. <!-- Like Song Button -->
  178. <button
  179. class="button is-success like-song"
  180. id="like-song"
  181. @click="toggleLike()"
  182. >
  183. <i
  184. class="material-icons icon-with-button"
  185. :class="{ liked: liked }"
  186. >thumb_up_alt</i
  187. >{{ currentSong.likes }}
  188. </button>
  189. <!-- Dislike Song Button -->
  190. <button
  191. class="button is-danger dislike-song"
  192. id="dislike-song"
  193. @click="toggleDislike()"
  194. >
  195. <i
  196. class="material-icons icon-with-button"
  197. :class="{
  198. disliked: disliked
  199. }"
  200. >thumb_down_alt</i
  201. >{{ currentSong.dislikes }}
  202. </button>
  203. </div>
  204. <!-- Add Song To Playlist Button & Dropdown -->
  205. <div
  206. id="add-song-to-playlist"
  207. v-click-outside="
  208. () =>
  209. (this.showPlaylistDropdown = false)
  210. "
  211. >
  212. <div class="control has-addons">
  213. <button
  214. class="button is-primary"
  215. @click="
  216. showPlaylistDropdown = !showPlaylistDropdown
  217. "
  218. >
  219. <i class="material-icons"
  220. >queue</i
  221. >
  222. </button>
  223. <button
  224. class="button"
  225. id="dropdown-toggle"
  226. @click="
  227. showPlaylistDropdown = !showPlaylistDropdown
  228. "
  229. >
  230. <i class="material-icons">
  231. {{
  232. showPlaylistDropdown
  233. ? "expand_more"
  234. : "expand_less"
  235. }}
  236. </i>
  237. </button>
  238. </div>
  239. <add-to-playlist-dropdown
  240. v-if="showPlaylistDropdown"
  241. />
  242. </div>
  243. </div>
  244. <div id="right-buttons" v-else>
  245. <!-- Disabled Ratings (Like/Dislike) Buttons -->
  246. <div
  247. id="ratings"
  248. v-if="
  249. currentSong.likes !== -1 &&
  250. currentSong.dislikes !== -1
  251. "
  252. >
  253. <!-- Disabled Like Song Button -->
  254. <button
  255. class="button is-success tooltip tooltip-top disabled"
  256. id="like-song"
  257. data-tooltip="Login to like songs"
  258. >
  259. <i
  260. class="material-icons icon-with-button"
  261. >thumb_up_alt</i
  262. >{{ currentSong.likes }}
  263. </button>
  264. <!-- Disabled Dislike Song Button -->
  265. <button
  266. class="button is-danger tooltip tooltip-top disabled"
  267. id="dislike-song"
  268. data-tooltip="Login to dislike songs"
  269. >
  270. <i
  271. class="material-icons icon-with-button"
  272. >thumb_down_alt</i
  273. >{{ currentSong.dislikes }}
  274. </button>
  275. </div>
  276. <!-- Disabled Add Song To Playlist Button & Dropdown -->
  277. <div id="add-song-to-playlist">
  278. <div class="control has-addons">
  279. <button
  280. class="button is-primary tooltip tooltip-top disabled"
  281. data-tooltip="Login to add songs to playlist"
  282. >
  283. <i class="material-icons"
  284. >queue</i
  285. >
  286. </button>
  287. </div>
  288. </div>
  289. </div>
  290. </div>
  291. </div>
  292. <p
  293. class="player-container nothing-here-text"
  294. v-if="noSong"
  295. >
  296. No song is currently playing
  297. </p>
  298. <div v-if="!noSong" id="current-next-row">
  299. <div
  300. id="currently-playing-container"
  301. class="quadrant"
  302. :class="{ 'no-currently-playing': noSong }"
  303. >
  304. <currently-playing />
  305. <!-- <p v-else class="nothing-here-text">
  306. No song is currently playing
  307. </p> -->
  308. </div>
  309. </div>
  310. </div>
  311. <div id="station-right-column" class="column">
  312. <div id="about-station-container" class="quadrant">
  313. <div id="station-info">
  314. <div class="row" id="station-name">
  315. <h1>{{ station.displayName }}</h1>
  316. <a href="#">
  317. <!-- Favorite Station Button -->
  318. <i
  319. v-if="
  320. loggedIn && station.isFavorited
  321. "
  322. @click.prevent="unfavoriteStation()"
  323. class="material-icons"
  324. >star</i
  325. >
  326. <i
  327. v-if="
  328. loggedIn && !station.isFavorited
  329. "
  330. @click.prevent="favoriteStation()"
  331. class="material-icons"
  332. >star_border</i
  333. >
  334. </a>
  335. </div>
  336. <p>{{ station.description }}</p>
  337. </div>
  338. <div id="admin-buttons" v-if="isOwnerOrAdmin()">
  339. <!-- (Admin) Pause/Resume Button -->
  340. <button
  341. class="button is-danger"
  342. v-if="stationPaused"
  343. @click="resumeStation()"
  344. >
  345. <i class="material-icons icon-with-button"
  346. >play_arrow</i
  347. >
  348. <span class="optional-desktop-only-text">
  349. Resume Station
  350. </span>
  351. </button>
  352. <button
  353. class="button is-danger"
  354. @click="pauseStation()"
  355. v-else
  356. >
  357. <i class="material-icons icon-with-button"
  358. >pause</i
  359. >
  360. <span class="optional-desktop-only-text">
  361. Pause Station
  362. </span>
  363. </button>
  364. <!-- (Admin) Skip Button -->
  365. <button
  366. class="button is-danger"
  367. @click="skipStation()"
  368. >
  369. <i class="material-icons icon-with-button"
  370. >skip_next</i
  371. >
  372. <span class="optional-desktop-only-text">
  373. Force Skip
  374. </span>
  375. </button>
  376. <!-- (Admin) Station Settings Button -->
  377. <button
  378. class="button is-primary"
  379. @click="
  380. openModal({
  381. sector: 'station',
  382. modal: 'editStation'
  383. })
  384. "
  385. >
  386. <i class="material-icons icon-with-button"
  387. >settings</i
  388. >
  389. <span class="optional-desktop-only-text">
  390. Station settings
  391. </span>
  392. </button>
  393. </div>
  394. </div>
  395. <div id="sidebar-container" class="quadrant">
  396. <station-sidebar />
  397. </div>
  398. </div>
  399. </div>
  400. <song-queue v-if="modals.addSongToQueue" />
  401. <edit-playlist v-if="modals.editPlaylist" />
  402. <create-playlist v-if="modals.createPlaylist" />
  403. <edit-station
  404. v-if="modals.editStation"
  405. :station-id="station._id"
  406. sector="station"
  407. />
  408. <report v-if="modals.report" />
  409. </div>
  410. <main-footer v-if="exists" />
  411. </div>
  412. <edit-song
  413. v-if="modals.editSong"
  414. :song-id="editingSongId"
  415. song-type="songs"
  416. sector="station"
  417. />
  418. <floating-box id="player-debug-box" ref="playerDebugBox">
  419. <template #body>
  420. <span><b>YouTube id</b>: {{ currentSong.songId }}</span>
  421. <span><b>Duration</b>: {{ currentSong.duration }}</span>
  422. <span
  423. ><b>Skip duration</b>: {{ currentSong.skipDuration }}</span
  424. >
  425. <span><b>Can autoplay</b>: {{ canAutoplay }}</span>
  426. <span
  427. ><b>Attempts to play video</b>:
  428. {{ attemptsToPlayVideo }}</span
  429. >
  430. <span
  431. ><b>Last time requested if can autoplay</b>:
  432. {{ lastTimeRequestedIfCanAutoplay }}</span
  433. >
  434. <span><b>Loading</b>: {{ loading }}</span>
  435. <span><b>Playback rate</b>: {{ playbackRate }}</span>
  436. <span><b>Player ready</b>: {{ playerReady }}</span>
  437. <span><b>Ready</b>: {{ ready }}</span>
  438. <span><b>Seeking</b>: {{ seeking }}</span>
  439. <span><b>System difference</b>: {{ systemDifference }}</span>
  440. <span><b>Time before paused</b>: {{ timeBeforePause }}</span>
  441. <span><b>Time elapsed</b>: {{ timeElapsed }}</span>
  442. <span><b>Time paused</b>: {{ timePaused }}</span>
  443. <span><b>Volume slider value</b>: {{ volumeSliderValue }}</span>
  444. <span><b>Local paused</b>: {{ localPaused }}</span>
  445. <span><b>No song</b>: {{ noSong }}</span>
  446. <span
  447. ><b>Private playlist queue selected</b>:
  448. {{ privatePlaylistQueueSelected }}</span
  449. >
  450. <span><b>Station paused</b>: {{ stationPaused }}</span>
  451. <span
  452. ><b>Station Genres</b>:
  453. {{ station.genres.join(", ") }}</span
  454. >
  455. <span
  456. ><b>Station Blacklisted Genres</b>:
  457. {{ station.blacklistedGenres.join(", ") }}</span
  458. >
  459. </template>
  460. </floating-box>
  461. <Z404 v-if="!exists"></Z404>
  462. </div>
  463. </template>
  464. <script>
  465. import { mapState, mapActions } from "vuex";
  466. import Toast from "toasters";
  467. import { ContentLoader } from "vue-content-loader";
  468. import MainHeader from "../../components/layout/MainHeader.vue";
  469. import MainFooter from "../../components/layout/MainFooter.vue";
  470. import Z404 from "../404.vue";
  471. import FloatingBox from "../../components/ui/FloatingBox.vue";
  472. import AddToPlaylistDropdown from "./components/AddToPlaylistDropdown.vue";
  473. import io from "../../io";
  474. import keyboardShortcuts from "../../keyboardShortcuts";
  475. import utils from "../../../js/utils";
  476. import CurrentlyPlaying from "./components/CurrentlyPlaying.vue";
  477. import StationSidebar from "./components/Sidebar/index.vue";
  478. export default {
  479. components: {
  480. ContentLoader,
  481. MainHeader,
  482. MainFooter,
  483. SongQueue: () => import("./AddSongToQueue.vue"),
  484. EditPlaylist: () =>
  485. import("../../components/modals/EditPlaylist/index.vue"),
  486. CreatePlaylist: () =>
  487. import("../../components/modals/CreatePlaylist.vue"),
  488. EditStation: () => import("../../components/modals/EditStation.vue"),
  489. Report: () => import("./Report.vue"),
  490. Z404,
  491. FloatingBox,
  492. CurrentlyPlaying,
  493. StationSidebar,
  494. AddToPlaylistDropdown,
  495. EditSong: () => import("../../components/modals/EditSong.vue")
  496. },
  497. data() {
  498. return {
  499. utils,
  500. title: "Station",
  501. loading: true,
  502. ready: false,
  503. exists: true,
  504. playerReady: false,
  505. player: undefined,
  506. timePaused: 0,
  507. muted: false,
  508. timeElapsed: "0:00",
  509. liked: false,
  510. disliked: false,
  511. timeBeforePause: 0,
  512. skipVotes: 0,
  513. automaticallyRequestedSongId: null,
  514. systemDifference: 0,
  515. attemptsToPlayVideo: 0,
  516. canAutoplay: true,
  517. lastTimeRequestedIfCanAutoplay: 0,
  518. seeking: false,
  519. playbackRate: 1,
  520. volumeSliderValue: 0,
  521. showPlaylistDropdown: false,
  522. editingSongId: "",
  523. theme: "var(--primary-color)"
  524. };
  525. },
  526. computed: {
  527. ...mapState("modalVisibility", {
  528. modals: state => state.modals.station
  529. }),
  530. ...mapState("station", {
  531. station: state => state.station,
  532. currentSong: state => state.currentSong,
  533. songsList: state => state.songsList,
  534. stationPaused: state => state.stationPaused,
  535. localPaused: state => state.localPaused,
  536. noSong: state => state.noSong,
  537. privatePlaylistQueueSelected: state =>
  538. state.privatePlaylistQueueSelected
  539. }),
  540. ...mapState({
  541. loggedIn: state => state.user.auth.loggedIn,
  542. userId: state => state.user.auth.userId,
  543. role: state => state.user.auth.role,
  544. nightmode: state => state.user.preferences.nightmode,
  545. autoSkipDisliked: state => state.user.preferences.autoSkipDisliked
  546. })
  547. },
  548. mounted() {
  549. window.scrollTo(0, 0);
  550. Date.currently = () => {
  551. return new Date().getTime() + this.systemDifference;
  552. };
  553. this.stationName = this.$route.params.id;
  554. window.stationInterval = 0;
  555. io.getSocket(socket => {
  556. this.socket = socket;
  557. if (this.socket.connected) this.join();
  558. io.onConnect(this.join);
  559. this.socket.emit("stations.existsByName", this.stationName, res => {
  560. if (res.status === "failure" || !res.exists) {
  561. this.loading = false;
  562. this.exists = false;
  563. }
  564. });
  565. this.socket.on("event:songs.next", data => {
  566. const previousSong = this.currentSong.songId
  567. ? this.currentSong
  568. : null;
  569. this.updatePreviousSong(previousSong);
  570. const { currentSong } = data;
  571. if (currentSong && !currentSong.thumbnail)
  572. currentSong.ytThumbnail = `https://img.youtube.com/vi/${currentSong.songId}/mqdefault.jpg`;
  573. this.updateCurrentSong(currentSong || {});
  574. this.startedAt = data.startedAt;
  575. this.updateStationPaused(data.paused);
  576. this.timePaused = data.timePaused;
  577. if (currentSong) {
  578. this.updateNoSong(false);
  579. if (this.currentSong.artists)
  580. this.currentSong.artists = this.currentSong.artists.join(
  581. ", "
  582. );
  583. if (!this.playerReady) this.youtubeReady();
  584. else this.playVideo();
  585. this.socket.emit(
  586. "songs.getOwnSongRatings",
  587. data.currentSong.songId,
  588. song => {
  589. if (this.currentSong.songId === song.songId) {
  590. this.liked = song.liked;
  591. this.disliked = song.disliked;
  592. if (
  593. this.autoSkipDisliked &&
  594. song.disliked === true
  595. ) {
  596. this.voteSkipStation();
  597. new Toast({
  598. content:
  599. "Automatically voted to skip disliked song.",
  600. timeout: 4000
  601. });
  602. }
  603. }
  604. }
  605. );
  606. } else {
  607. if (this.playerReady) this.player.pauseVideo();
  608. this.updateNoSong(true);
  609. }
  610. let isInQueue = false;
  611. this.songsList.forEach(queueSong => {
  612. if (queueSong.requestedBy === this.userId) isInQueue = true;
  613. });
  614. if (
  615. !isInQueue &&
  616. this.privatePlaylistQueueSelected &&
  617. (this.automaticallyRequestedSongId !==
  618. this.currentSong.songId ||
  619. !this.currentSong.songId)
  620. ) {
  621. this.addFirstPrivatePlaylistSongToQueue();
  622. }
  623. });
  624. this.socket.on("event:stations.pause", data => {
  625. this.pausedAt = data.pausedAt;
  626. this.updateStationPaused(true);
  627. this.pauseLocalPlayer();
  628. });
  629. this.socket.on("event:stations.resume", data => {
  630. this.timePaused = data.timePaused;
  631. this.updateStationPaused(false);
  632. if (!this.localPaused) this.resumeLocalPlayer();
  633. });
  634. this.socket.on("event:stations.remove", () => {
  635. window.location.href = "/";
  636. return true;
  637. });
  638. this.socket.on("event:song.like", data => {
  639. if (!this.noSong) {
  640. if (data.songId === this.currentSong.songId) {
  641. this.currentSong.dislikes = data.dislikes;
  642. this.currentSong.likes = data.likes;
  643. }
  644. }
  645. });
  646. this.socket.on("event:song.dislike", data => {
  647. if (!this.noSong) {
  648. if (data.songId === this.currentSong.songId) {
  649. this.currentSong.dislikes = data.dislikes;
  650. this.currentSong.likes = data.likes;
  651. }
  652. }
  653. });
  654. this.socket.on("event:song.unlike", data => {
  655. if (!this.noSong) {
  656. if (data.songId === this.currentSong.songId) {
  657. this.currentSong.dislikes = data.dislikes;
  658. this.currentSong.likes = data.likes;
  659. }
  660. }
  661. });
  662. this.socket.on("event:song.undislike", data => {
  663. if (!this.noSong) {
  664. if (data.songId === this.currentSong.songId) {
  665. this.currentSong.dislikes = data.dislikes;
  666. this.currentSong.likes = data.likes;
  667. }
  668. }
  669. });
  670. this.socket.on("event:song.newRatings", data => {
  671. if (!this.noSong) {
  672. if (data.songId === this.currentSong.songId) {
  673. this.liked = data.liked;
  674. this.disliked = data.disliked;
  675. }
  676. }
  677. });
  678. this.socket.on("event:queue.update", queue => {
  679. if (this.station.type === "community")
  680. this.updateSongsList(queue);
  681. });
  682. this.socket.on("event:song.voteSkipSong", () => {
  683. if (this.currentSong) this.currentSong.skipVotes += 1;
  684. });
  685. this.socket.on("event:privatePlaylist.selected", playlistId => {
  686. if (this.station.type === "community") {
  687. this.station.privatePlaylist = playlistId;
  688. }
  689. });
  690. this.socket.on("event:privatePlaylist.deselected", () => {
  691. if (this.station.type === "community") {
  692. this.station.privatePlaylist = null;
  693. }
  694. });
  695. this.socket.on("event:partyMode.updated", partyMode => {
  696. if (this.station.type === "community") {
  697. this.station.partyMode = partyMode;
  698. }
  699. });
  700. this.socket.on("event:theme.updated", theme => {
  701. this.station.theme = theme;
  702. });
  703. this.socket.on("event:newOfficialPlaylist", playlist => {
  704. if (this.station.type === "official")
  705. this.updateSongsList(playlist);
  706. });
  707. this.socket.on("event:users.updated", users =>
  708. this.updateUsers(users)
  709. );
  710. this.socket.on("event:userCount.updated", userCount =>
  711. this.updateUserCount(userCount)
  712. );
  713. this.socket.on("event:queueLockToggled", locked => {
  714. this.station.locked = locked;
  715. });
  716. this.socket.on("event:user.favoritedStation", stationId => {
  717. if (stationId === this.station._id)
  718. this.updateIfStationIsFavorited({ isFavorited: true });
  719. });
  720. this.socket.on("event:user.unfavoritedStation", stationId => {
  721. if (stationId === this.station._id)
  722. this.updateIfStationIsFavorited({ isFavorited: false });
  723. });
  724. });
  725. if (JSON.parse(localStorage.getItem("muted"))) {
  726. this.muted = true;
  727. this.player.setVolume(0);
  728. this.volumeSliderValue = 0 * 100;
  729. } else {
  730. let volume = parseFloat(localStorage.getItem("volume"));
  731. volume =
  732. typeof volume === "number" && !Number.isNaN(volume)
  733. ? volume
  734. : 20;
  735. localStorage.setItem("volume", volume);
  736. this.volumeSliderValue = volume * 100;
  737. }
  738. },
  739. beforeDestroy() {
  740. /** Reset Songslist */
  741. this.updateSongsList([]);
  742. const shortcutNames = [
  743. "station.pauseResume",
  744. "station.skipStation",
  745. "station.lowerVolumeLarge",
  746. "station.lowerVolumeSmall",
  747. "station.increaseVolumeLarge",
  748. "station.increaseVolumeSmall",
  749. "station.toggleDebug"
  750. ];
  751. shortcutNames.forEach(shortcutName => {
  752. keyboardShortcuts.unregisterShortcut(shortcutName);
  753. });
  754. },
  755. methods: {
  756. isOwnerOnly() {
  757. return this.loggedIn && this.userId === this.station.owner;
  758. },
  759. isAdminOnly() {
  760. return this.loggedIn && this.role === "admin";
  761. },
  762. isOwnerOrAdmin() {
  763. return this.isOwnerOnly() || this.isAdminOnly();
  764. },
  765. removeFromQueue(songId) {
  766. window.socket.emit(
  767. "stations.removeFromQueue",
  768. this.station._id,
  769. songId,
  770. res => {
  771. if (res.status === "success") {
  772. new Toast({
  773. content:
  774. "Successfully removed song from the queue.",
  775. timeout: 4000
  776. });
  777. } else new Toast({ content: res.message, timeout: 8000 });
  778. }
  779. );
  780. },
  781. youtubeReady() {
  782. if (!this.player) {
  783. this.player = new window.YT.Player("stationPlayer", {
  784. height: 270,
  785. width: 480,
  786. videoId: this.currentSong.songId,
  787. host: "https://www.youtube-nocookie.com",
  788. startSeconds:
  789. this.getTimeElapsed() / 1000 +
  790. this.currentSong.skipDuration,
  791. playerVars: {
  792. controls: 0,
  793. iv_load_policy: 3,
  794. rel: 0,
  795. showinfo: 0,
  796. disablekb: 1
  797. },
  798. events: {
  799. onReady: () => {
  800. this.playerReady = true;
  801. let volume = parseInt(
  802. localStorage.getItem("volume")
  803. );
  804. volume = typeof volume === "number" ? volume : 20;
  805. this.player.setVolume(volume);
  806. if (volume > 0) this.player.unMute();
  807. if (this.muted) this.player.mute();
  808. this.playVideo();
  809. },
  810. onError: err => {
  811. console.log("error with youtube video", err);
  812. if (err.data === 150 && this.loggedIn) {
  813. new Toast({
  814. content:
  815. "Automatically voted to skip as this song isn't available for you.",
  816. timeout: 4000
  817. });
  818. // automatically vote to skip
  819. this.voteSkipStation();
  820. // persistent message while song is playing
  821. const toastMessage =
  822. "This song is unavailable for you, but is playing for everyone else.";
  823. new Toast({
  824. content: toastMessage,
  825. persistant: true
  826. });
  827. // save current song id
  828. const erroredSongId = this.currentSong.songId;
  829. // remove persistent toast if video has finished
  830. window.isSongErroredInterval = setInterval(
  831. () => {
  832. if (
  833. this.currentSong.songId !==
  834. erroredSongId
  835. ) {
  836. document
  837. .getElementById(
  838. "toasts-content"
  839. )
  840. .childNodes.forEach(toast => {
  841. if (
  842. toast.innerHTML ===
  843. toastMessage
  844. )
  845. toast.remove();
  846. });
  847. clearInterval(
  848. window.isSongErroredInterval
  849. );
  850. }
  851. },
  852. 150
  853. );
  854. } else {
  855. new Toast({
  856. content:
  857. "There has been an error with the YouTube Embed",
  858. timeout: 8000
  859. });
  860. }
  861. },
  862. onStateChange: event => {
  863. if (
  864. event.data === window.YT.PlayerState.PLAYING &&
  865. this.videoLoading === true
  866. ) {
  867. this.videoLoading = false;
  868. this.player.seekTo(
  869. this.getTimeElapsed() / 1000 +
  870. this.currentSong.skipDuration,
  871. true
  872. );
  873. if (this.localPaused || this.stationPaused)
  874. this.player.pauseVideo();
  875. } else if (
  876. event.data === window.YT.PlayerState.PLAYING &&
  877. (this.localPaused || this.stationPaused)
  878. ) {
  879. this.player.seekTo(
  880. this.timeBeforePause / 1000,
  881. true
  882. );
  883. this.player.pauseVideo();
  884. } else if (
  885. event.data === window.YT.PlayerState.PLAYING &&
  886. this.seeking === true
  887. ) {
  888. this.seeking = false;
  889. }
  890. if (
  891. event.data === window.YT.PlayerState.PAUSED &&
  892. !this.localPaused &&
  893. !this.stationPaused &&
  894. !this.noSong &&
  895. this.player.getDuration() / 1000 <
  896. this.currentSong.duration
  897. ) {
  898. this.player.seekTo(
  899. this.getTimeElapsed() / 1000 +
  900. this.currentSong.skipDuration,
  901. true
  902. );
  903. this.player.playVideo();
  904. }
  905. }
  906. }
  907. });
  908. }
  909. },
  910. getTimeElapsed() {
  911. if (this.currentSong) {
  912. let { timePaused } = this;
  913. if (this.stationPaused)
  914. timePaused += Date.currently() - this.pausedAt;
  915. return Date.currently() - this.startedAt - timePaused;
  916. }
  917. return 0;
  918. },
  919. playVideo() {
  920. if (this.playerReady) {
  921. this.videoLoading = true;
  922. this.player.loadVideoById(
  923. this.currentSong.songId,
  924. this.getTimeElapsed() / 1000 + this.currentSong.skipDuration
  925. );
  926. if (window.stationInterval !== 0)
  927. clearInterval(window.stationInterval);
  928. window.stationInterval = setInterval(() => {
  929. this.resizeSeekerbar();
  930. this.calculateTimeElapsed();
  931. }, 150);
  932. }
  933. },
  934. resizeSeekerbar() {
  935. if (!this.stationPaused) {
  936. document.getElementById(
  937. "seeker-bar"
  938. ).style.width = `${parseFloat(
  939. (this.getTimeElapsed() / 1000 / this.currentSong.duration) *
  940. 100
  941. )}%`;
  942. }
  943. },
  944. calculateTimeElapsed() {
  945. if (
  946. this.playerReady &&
  947. this.currentSong &&
  948. this.player.getPlayerState() === -1
  949. ) {
  950. if (this.attemptsToPlayVideo >= 5) {
  951. if (
  952. Date.now() - this.lastTimeRequestedIfCanAutoplay >
  953. 2000
  954. ) {
  955. this.lastTimeRequestedIfCanAutoplay = Date.now();
  956. window.canAutoplay.video().then(({ result }) => {
  957. if (result) {
  958. this.attemptsToPlayVideo = 0;
  959. this.canAutoplay = true;
  960. } else {
  961. this.canAutoplay = false;
  962. }
  963. });
  964. }
  965. } else {
  966. this.player.playVideo();
  967. this.attemptsToPlayVideo += 1;
  968. }
  969. }
  970. if (!this.stationPaused && !this.localPaused) {
  971. const timeElapsed = this.getTimeElapsed();
  972. const currentPlayerTime =
  973. Math.max(
  974. this.player.getCurrentTime() -
  975. this.currentSong.skipDuration,
  976. 0
  977. ) * 1000;
  978. const difference = timeElapsed - currentPlayerTime;
  979. // console.log(difference);
  980. let playbackRate = 1;
  981. if (difference < -2000) {
  982. if (!this.seeking) {
  983. this.seeking = true;
  984. this.player.seekTo(
  985. this.getTimeElapsed() / 1000 +
  986. this.currentSong.skipDuration
  987. );
  988. }
  989. } else if (difference < -200) {
  990. // console.log("Difference0.8");
  991. playbackRate = 0.8;
  992. } else if (difference < -50) {
  993. // console.log("Difference0.9");
  994. playbackRate = 0.9;
  995. } else if (difference < -25) {
  996. // console.log("Difference0.99");
  997. playbackRate = 0.95;
  998. } else if (difference > 2000) {
  999. if (!this.seeking) {
  1000. this.seeking = true;
  1001. this.player.seekTo(
  1002. this.getTimeElapsed() / 1000 +
  1003. this.currentSong.skipDuration
  1004. );
  1005. }
  1006. } else if (difference > 200) {
  1007. // console.log("Difference1.2");
  1008. playbackRate = 1.2;
  1009. } else if (difference > 50) {
  1010. // console.log("Difference1.1");
  1011. playbackRate = 1.1;
  1012. } else if (difference > 25) {
  1013. // console.log("Difference1.01");
  1014. playbackRate = 1.05;
  1015. } else if (this.player.getPlaybackRate !== 1.0) {
  1016. // console.log("NDifference1.0");
  1017. this.player.setPlaybackRate(1.0);
  1018. }
  1019. if (this.playbackRate !== playbackRate) {
  1020. this.player.setPlaybackRate(playbackRate);
  1021. this.playbackRate = playbackRate;
  1022. }
  1023. }
  1024. /* if (this.currentTime !== undefined && this.paused) {
  1025. this.timePaused += Date.currently() - this.currentTime;
  1026. this.currentTime = undefined;
  1027. } */
  1028. let { timePaused } = this;
  1029. if (this.stationPaused)
  1030. timePaused += Date.currently() - this.pausedAt;
  1031. const duration =
  1032. (Date.currently() - this.startedAt - timePaused) / 1000;
  1033. const songDuration = this.currentSong.duration;
  1034. if (songDuration <= duration) this.player.pauseVideo();
  1035. if (!this.stationPaused && duration <= songDuration)
  1036. this.timeElapsed = utils.formatTime(duration);
  1037. },
  1038. toggleLock() {
  1039. window.socket.emit("stations.toggleLock", this.station._id, res => {
  1040. if (res.status === "success") {
  1041. new Toast({
  1042. content: "Successfully toggled the queue lock.",
  1043. timeout: 4000
  1044. });
  1045. } else new Toast({ content: res.message, timeout: 8000 });
  1046. });
  1047. },
  1048. changeVolume() {
  1049. const volume = this.volumeSliderValue;
  1050. localStorage.setItem("volume", volume / 100);
  1051. if (this.playerReady) {
  1052. this.player.setVolume(volume / 100);
  1053. if (volume > 0) {
  1054. this.player.unMute();
  1055. localStorage.setItem("muted", false);
  1056. this.muted = false;
  1057. }
  1058. }
  1059. },
  1060. resumeLocalStation() {
  1061. this.updateLocalPaused(false);
  1062. if (!this.stationPaused) this.resumeLocalPlayer();
  1063. },
  1064. pauseLocalStation() {
  1065. this.updateLocalPaused(true);
  1066. this.pauseLocalPlayer();
  1067. },
  1068. resumeLocalPlayer() {
  1069. if (!this.noSong) {
  1070. if (this.playerReady) {
  1071. this.player.seekTo(
  1072. this.getTimeElapsed() / 1000 +
  1073. this.currentSong.skipDuration
  1074. );
  1075. this.player.playVideo();
  1076. }
  1077. }
  1078. },
  1079. pauseLocalPlayer() {
  1080. if (!this.noSong) {
  1081. this.timeBeforePause = this.getTimeElapsed();
  1082. if (this.playerReady) this.player.pauseVideo();
  1083. }
  1084. },
  1085. skipStation() {
  1086. this.socket.emit("stations.forceSkip", this.station._id, data => {
  1087. if (data.status !== "success")
  1088. new Toast({
  1089. content: `Error: ${data.message}`,
  1090. timeout: 8000
  1091. });
  1092. else
  1093. new Toast({
  1094. content:
  1095. "Successfully skipped the station's current song.",
  1096. timeout: 4000
  1097. });
  1098. });
  1099. },
  1100. voteSkipStation() {
  1101. this.socket.emit("stations.voteSkip", this.station._id, data => {
  1102. if (data.status !== "success")
  1103. new Toast({
  1104. content: `Error: ${data.message}`,
  1105. timeout: 8000
  1106. });
  1107. else
  1108. new Toast({
  1109. content: "Successfully voted to skip the current song.",
  1110. timeout: 4000
  1111. });
  1112. });
  1113. },
  1114. resumeStation() {
  1115. this.socket.emit("stations.resume", this.station._id, data => {
  1116. if (data.status !== "success")
  1117. new Toast({
  1118. content: `Error: ${data.message}`,
  1119. timeout: 8000
  1120. });
  1121. else
  1122. new Toast({
  1123. content: "Successfully resumed the station.",
  1124. timeout: 4000
  1125. });
  1126. });
  1127. },
  1128. pauseStation() {
  1129. this.socket.emit("stations.pause", this.station._id, data => {
  1130. if (data.status !== "success")
  1131. new Toast({
  1132. content: `Error: ${data.message}`,
  1133. timeout: 8000
  1134. });
  1135. else
  1136. new Toast({
  1137. content: "Successfully paused the station.",
  1138. timeout: 4000
  1139. });
  1140. });
  1141. },
  1142. toggleMute() {
  1143. if (this.playerReady) {
  1144. const previousVolume = parseFloat(
  1145. localStorage.getItem("volume")
  1146. );
  1147. const volume =
  1148. this.player.getVolume() * 100 <= 0 ? previousVolume : 0;
  1149. this.muted = !this.muted;
  1150. localStorage.setItem("muted", this.muted);
  1151. this.volumeSliderValue = volume * 100;
  1152. this.player.setVolume(volume);
  1153. if (!this.muted) localStorage.setItem("volume", volume);
  1154. }
  1155. },
  1156. increaseVolume() {
  1157. if (this.playerReady) {
  1158. const previousVolume = parseInt(localStorage.getItem("volume"));
  1159. let volume = previousVolume + 5;
  1160. if (previousVolume === 0) {
  1161. this.muted = false;
  1162. localStorage.setItem("muted", false);
  1163. }
  1164. if (volume > 100) volume = 100;
  1165. this.volumeSliderValue = volume * 100;
  1166. this.player.setVolume(volume);
  1167. localStorage.setItem("volume", volume);
  1168. }
  1169. },
  1170. toggleLike() {
  1171. if (this.liked)
  1172. this.socket.emit(
  1173. "songs.unlike",
  1174. this.currentSong.songId,
  1175. data => {
  1176. if (data.status !== "success")
  1177. new Toast({
  1178. content: `Error: ${data.message}`,
  1179. timeout: 8000
  1180. });
  1181. }
  1182. );
  1183. else
  1184. this.socket.emit(
  1185. "songs.like",
  1186. this.currentSong.songId,
  1187. data => {
  1188. if (data.status !== "success")
  1189. new Toast({
  1190. content: `Error: ${data.message}`,
  1191. timeout: 8000
  1192. });
  1193. }
  1194. );
  1195. },
  1196. toggleDislike() {
  1197. if (this.disliked)
  1198. return this.socket.emit(
  1199. "songs.undislike",
  1200. this.currentSong.songId,
  1201. data => {
  1202. if (data.status !== "success")
  1203. new Toast({
  1204. content: `Error: ${data.message}`,
  1205. timeout: 8000
  1206. });
  1207. }
  1208. );
  1209. return this.socket.emit(
  1210. "songs.dislike",
  1211. this.currentSong.songId,
  1212. data => {
  1213. if (data.status !== "success")
  1214. new Toast({
  1215. content: `Error: ${data.message}`,
  1216. timeout: 8000
  1217. });
  1218. }
  1219. );
  1220. },
  1221. addFirstPrivatePlaylistSongToQueue() {
  1222. let isInQueue = false;
  1223. if (
  1224. this.station.type === "community" &&
  1225. this.station.partyMode === true
  1226. ) {
  1227. this.songsList.forEach(queueSong => {
  1228. if (queueSong.requestedBy === this.userId) isInQueue = true;
  1229. });
  1230. if (!isInQueue && this.privatePlaylistQueueSelected) {
  1231. this.socket.emit(
  1232. "playlists.getFirstSong",
  1233. this.privatePlaylistQueueSelected,
  1234. data => {
  1235. if (data.status === "success") {
  1236. if (data.song) {
  1237. if (data.song.duration < 15 * 60) {
  1238. this.automaticallyRequestedSongId =
  1239. data.song.songId;
  1240. this.socket.emit(
  1241. "stations.addToQueue",
  1242. this.station._id,
  1243. data.song.songId,
  1244. data2 => {
  1245. if (data2.status === "success")
  1246. this.socket.emit(
  1247. "playlists.moveSongToBottom",
  1248. this
  1249. .privatePlaylistQueueSelected,
  1250. data.song.songId
  1251. );
  1252. }
  1253. );
  1254. } else {
  1255. new Toast({
  1256. content: `Top song in playlist was too long to be added.`,
  1257. timeout: 3000
  1258. });
  1259. this.socket.emit(
  1260. "playlists.moveSongToBottom",
  1261. this.privatePlaylistQueueSelected,
  1262. data.song.songId,
  1263. data3 => {
  1264. if (data3.status === "success")
  1265. setTimeout(
  1266. () =>
  1267. this.addFirstPrivatePlaylistSongToQueue(),
  1268. 3000
  1269. );
  1270. }
  1271. );
  1272. }
  1273. } else
  1274. new Toast({
  1275. content: `Selected playlist has no songs.`,
  1276. timeout: 4000
  1277. });
  1278. }
  1279. }
  1280. );
  1281. }
  1282. }
  1283. },
  1284. togglePlayerDebugBox() {
  1285. this.$refs.playerDebugBox.toggleBox();
  1286. },
  1287. resetPlayerDebugBox() {
  1288. this.$refs.playerDebugBox.resetBox();
  1289. },
  1290. join() {
  1291. this.socket.emit("stations.join", this.stationName, res => {
  1292. if (res.status === "success") {
  1293. setTimeout(() => {
  1294. this.loading = false;
  1295. }, 1000); // prevents popping in of youtube embed etc.
  1296. const {
  1297. _id,
  1298. displayName,
  1299. description,
  1300. privacy,
  1301. locked,
  1302. partyMode,
  1303. owner,
  1304. privatePlaylist,
  1305. type,
  1306. genres,
  1307. blacklistedGenres,
  1308. isFavorited,
  1309. theme
  1310. } = res.data;
  1311. this.joinStation({
  1312. _id,
  1313. name: this.stationName,
  1314. displayName,
  1315. description,
  1316. privacy,
  1317. locked,
  1318. partyMode,
  1319. owner,
  1320. privatePlaylist,
  1321. type,
  1322. genres,
  1323. blacklistedGenres,
  1324. isFavorited,
  1325. theme
  1326. });
  1327. const currentSong = res.data.currentSong
  1328. ? res.data.currentSong
  1329. : {};
  1330. if (currentSong.artists)
  1331. currentSong.artists = currentSong.artists.join(", ");
  1332. if (currentSong && !currentSong.thumbnail)
  1333. currentSong.ytThumbnail = `https://img.youtube.com/vi/${currentSong.songId}/mqdefault.jpg`;
  1334. this.updateCurrentSong(currentSong);
  1335. this.startedAt = res.data.startedAt;
  1336. this.updateStationPaused(res.data.paused);
  1337. this.timePaused = res.data.timePaused;
  1338. this.updateUserCount(res.data.userCount);
  1339. this.updateUsers(res.data.users);
  1340. this.pausedAt = res.data.pausedAt;
  1341. if (res.data.currentSong) {
  1342. this.updateNoSong(false);
  1343. this.youtubeReady();
  1344. this.playVideo();
  1345. this.socket.emit(
  1346. "songs.getOwnSongRatings",
  1347. res.data.currentSong.songId,
  1348. data => {
  1349. if (this.currentSong.songId === data.songId) {
  1350. this.liked = data.liked;
  1351. this.disliked = data.disliked;
  1352. }
  1353. }
  1354. );
  1355. } else {
  1356. if (this.playerReady) this.player.pauseVideo();
  1357. this.updateNoSong(true);
  1358. }
  1359. if (type === "community" && partyMode === true) {
  1360. this.socket.emit("stations.getQueue", _id, res => {
  1361. if (res.status === "success") {
  1362. this.updateSongsList(res.queue);
  1363. }
  1364. });
  1365. }
  1366. if (this.isOwnerOrAdmin()) {
  1367. keyboardShortcuts.registerShortcut(
  1368. "station.pauseResume",
  1369. {
  1370. keyCode: 32,
  1371. shift: false,
  1372. ctrl: true,
  1373. preventDefault: true,
  1374. handler: () => {
  1375. if (this.stationPaused)
  1376. this.resumeStation();
  1377. else this.pauseStation();
  1378. }
  1379. }
  1380. );
  1381. keyboardShortcuts.registerShortcut(
  1382. "station.skipStation",
  1383. {
  1384. keyCode: 39,
  1385. shift: false,
  1386. ctrl: true,
  1387. preventDefault: true,
  1388. handler: () => {
  1389. this.skipStation();
  1390. }
  1391. }
  1392. );
  1393. }
  1394. keyboardShortcuts.registerShortcut(
  1395. "station.lowerVolumeLarge",
  1396. {
  1397. keyCode: 40,
  1398. shift: false,
  1399. ctrl: true,
  1400. preventDefault: true,
  1401. handler: () => {
  1402. this.volumeSliderValue -= 1000;
  1403. this.changeVolume();
  1404. }
  1405. }
  1406. );
  1407. keyboardShortcuts.registerShortcut(
  1408. "station.lowerVolumeSmall",
  1409. {
  1410. keyCode: 40,
  1411. shift: true,
  1412. ctrl: true,
  1413. preventDefault: true,
  1414. handler: () => {
  1415. this.volumeSliderValue -= 100;
  1416. this.changeVolume();
  1417. }
  1418. }
  1419. );
  1420. keyboardShortcuts.registerShortcut(
  1421. "station.increaseVolumeLarge",
  1422. {
  1423. keyCode: 38,
  1424. shift: false,
  1425. ctrl: true,
  1426. preventDefault: true,
  1427. handler: () => {
  1428. this.volumeSliderValue += 1000;
  1429. this.changeVolume();
  1430. }
  1431. }
  1432. );
  1433. keyboardShortcuts.registerShortcut(
  1434. "station.increaseVolumeSmall",
  1435. {
  1436. keyCode: 38,
  1437. shift: true,
  1438. ctrl: true,
  1439. preventDefault: true,
  1440. handler: () => {
  1441. this.volumeSliderValue += 100;
  1442. this.changeVolume();
  1443. }
  1444. }
  1445. );
  1446. keyboardShortcuts.registerShortcut("station.toggleDebug", {
  1447. keyCode: 68,
  1448. shift: false,
  1449. ctrl: true,
  1450. preventDefault: true,
  1451. handler: () => {
  1452. this.togglePlayerDebugBox();
  1453. }
  1454. });
  1455. // UNIX client time before ping
  1456. const beforePing = Date.now();
  1457. this.socket.emit("apis.ping", pong => {
  1458. // UNIX client time after ping
  1459. const afterPing = Date.now();
  1460. // Average time in MS it took between the server responding and the client receiving
  1461. const connectionLatency = (afterPing - beforePing) / 2;
  1462. console.log(connectionLatency, beforePing - afterPing);
  1463. // UNIX server time
  1464. const serverDate = pong.date;
  1465. // Difference between the server UNIX time and the client UNIX time after ping, with the connectionLatency added to the server UNIX time
  1466. const difference =
  1467. serverDate + connectionLatency - afterPing;
  1468. console.log("Difference: ", difference);
  1469. if (difference > 3000 || difference < -3000) {
  1470. console.log(
  1471. "System time difference is bigger than 3 seconds."
  1472. );
  1473. }
  1474. this.systemDifference = difference;
  1475. });
  1476. } else {
  1477. this.loading = false;
  1478. this.exists = false;
  1479. }
  1480. });
  1481. },
  1482. favoriteStation() {
  1483. this.socket.emit(
  1484. "stations.favoriteStation",
  1485. this.station._id,
  1486. res => {
  1487. if (res.status === "success") {
  1488. new Toast({
  1489. content: "Successfully favorited station.",
  1490. timeout: 4000
  1491. });
  1492. } else new Toast({ content: res.message, timeout: 8000 });
  1493. }
  1494. );
  1495. },
  1496. unfavoriteStation() {
  1497. this.socket.emit(
  1498. "stations.unfavoriteStation",
  1499. this.station._id,
  1500. res => {
  1501. if (res.status === "success") {
  1502. new Toast({
  1503. content: "Successfully unfavorited station.",
  1504. timeout: 4000
  1505. });
  1506. } else new Toast({ content: res.message, timeout: 8000 });
  1507. }
  1508. );
  1509. },
  1510. editSong(song) {
  1511. this.editingSongId = song._id;
  1512. this.openModal({ sector: "station", modal: "editSong" });
  1513. },
  1514. ...mapActions("modalVisibility", ["openModal"]),
  1515. ...mapActions("station", [
  1516. "joinStation",
  1517. "updateUserCount",
  1518. "updateUsers",
  1519. "updateCurrentSong",
  1520. "updatePreviousSong",
  1521. "updateSongsList",
  1522. "updateStationPaused",
  1523. "updateLocalPaused",
  1524. "updateNoSong",
  1525. "updateIfStationIsFavorited"
  1526. ]),
  1527. ...mapActions("modals/editSong", ["stopVideo"])
  1528. }
  1529. };
  1530. </script>
  1531. <style lang="scss" scoped>
  1532. #page-loader-container {
  1533. height: inherit;
  1534. #page-loader-content {
  1535. height: inherit;
  1536. position: absolute;
  1537. max-width: 100%;
  1538. width: 1800px;
  1539. transform: translateX(-50%);
  1540. left: 50%;
  1541. }
  1542. #page-loader-layout {
  1543. height: inherit;
  1544. width: 100%;
  1545. }
  1546. }
  1547. #mobile-progress-animation {
  1548. width: 50px;
  1549. animation: rotate 0.8s infinite linear;
  1550. border: 8px solid var(--primary-color);
  1551. border-right-color: transparent;
  1552. border-radius: 50%;
  1553. height: 50px;
  1554. position: absolute;
  1555. top: 50%;
  1556. left: 50%;
  1557. display: none;
  1558. }
  1559. @keyframes rotate {
  1560. 0% {
  1561. transform: rotate(0deg);
  1562. }
  1563. 100% {
  1564. transform: rotate(360deg);
  1565. }
  1566. }
  1567. .nav,
  1568. .button.is-primary {
  1569. background-color: var(--primary-color) !important;
  1570. }
  1571. .button.is-primary:hover,
  1572. .button.is-primary:focus {
  1573. filter: brightness(90%);
  1574. }
  1575. #player-debug-box {
  1576. .box-body {
  1577. flex-direction: column;
  1578. b {
  1579. color: var(--black);
  1580. }
  1581. }
  1582. }
  1583. .night-mode {
  1584. #currently-playing-container,
  1585. #about-station-container,
  1586. #control-bar-container,
  1587. .player-container {
  1588. background-color: var(--dark-grey-3) !important;
  1589. }
  1590. #video-container,
  1591. #control-bar-container,
  1592. .quadrant:not(#sidebar-container),
  1593. .player-container {
  1594. border: 0 !important;
  1595. }
  1596. #seeker-bar-container {
  1597. background-color: var(--dark-grey-3) !important;
  1598. }
  1599. #dropdown-toggle {
  1600. background-color: var(--dark-grey-2) !important;
  1601. border: 0;
  1602. i {
  1603. color: var(--white);
  1604. }
  1605. }
  1606. }
  1607. #station-outer-container {
  1608. margin: 0 auto;
  1609. padding: 20px 40px;
  1610. height: 100%;
  1611. width: 100%;
  1612. max-width: 1800px;
  1613. display: flex;
  1614. #station-inner-container {
  1615. height: 100%;
  1616. width: 100%;
  1617. min-height: calc(100vh - 428px);
  1618. display: flex;
  1619. flex-direction: row;
  1620. flex-wrap: wrap;
  1621. .row {
  1622. display: flex;
  1623. flex-direction: row;
  1624. max-width: 100%;
  1625. }
  1626. .column {
  1627. display: flex;
  1628. flex-direction: column;
  1629. }
  1630. .quadrant {
  1631. border-radius: 5px;
  1632. margin: 10px;
  1633. flex-grow: 1;
  1634. }
  1635. .quadrant:not(#sidebar-container) {
  1636. background-color: var(--white);
  1637. border: 1px solid var(--light-grey-3);
  1638. }
  1639. #station-left-column {
  1640. padding: 0;
  1641. }
  1642. #station-right-column {
  1643. max-width: 650px;
  1644. padding: 0;
  1645. }
  1646. #about-station-container {
  1647. padding: 20px;
  1648. display: flex;
  1649. flex-direction: column;
  1650. flex-grow: unset;
  1651. #station-info {
  1652. #station-name {
  1653. flex-direction: row !important;
  1654. h1 {
  1655. margin: 0;
  1656. font-size: 36px;
  1657. line-height: 0.8;
  1658. }
  1659. i {
  1660. margin-left: 10px;
  1661. font-size: 30px;
  1662. color: var(--yellow);
  1663. }
  1664. }
  1665. p {
  1666. max-width: 700px;
  1667. margin-bottom: 10px;
  1668. }
  1669. }
  1670. #admin-buttons {
  1671. display: flex;
  1672. .button {
  1673. margin: 3px;
  1674. }
  1675. }
  1676. }
  1677. #current-next-row {
  1678. display: flex;
  1679. flex-direction: row;
  1680. #currently-playing-container,
  1681. #next-up-container {
  1682. overflow: hidden;
  1683. flex-basis: 50%;
  1684. .nothing-here-text {
  1685. height: 100%;
  1686. }
  1687. }
  1688. }
  1689. .player-container {
  1690. height: inherit;
  1691. background-color: var(--white);
  1692. display: flex;
  1693. flex-direction: column;
  1694. border: 1px solid var(--light-grey-3);
  1695. border-radius: 5px;
  1696. overflow: hidden;
  1697. flex-grow: 1;
  1698. &.nothing-here-text {
  1699. margin: 10px;
  1700. }
  1701. #video-container {
  1702. width: 100%;
  1703. height: 100%;
  1704. .player-cannot-autoplay {
  1705. position: relative;
  1706. width: 100%;
  1707. height: 100%;
  1708. bottom: calc(100% + 5px);
  1709. background: var(--primary-color);
  1710. display: flex;
  1711. align-items: center;
  1712. justify-content: center;
  1713. p {
  1714. color: var(--white);
  1715. font-size: 26px;
  1716. text-align: center;
  1717. }
  1718. }
  1719. }
  1720. #seeker-bar-container {
  1721. background-color: var(--white);
  1722. position: relative;
  1723. height: 7px;
  1724. display: block;
  1725. width: 100%;
  1726. overflow: hidden;
  1727. #seeker-bar {
  1728. background-color: var(--primary-color);
  1729. top: 0;
  1730. left: 0;
  1731. bottom: 0;
  1732. position: absolute;
  1733. }
  1734. }
  1735. #control-bar-container {
  1736. display: flex;
  1737. justify-content: space-around;
  1738. padding: 10px 0;
  1739. width: 100%;
  1740. background: var(--white);
  1741. flex-direction: column;
  1742. flex-flow: wrap;
  1743. .button:not(#dropdown-toggle) {
  1744. width: 75px;
  1745. }
  1746. #left-buttons,
  1747. #right-buttons {
  1748. margin: 3px;
  1749. }
  1750. #left-buttons {
  1751. display: flex;
  1752. .button:not(:first-of-type) {
  1753. margin-left: 5px;
  1754. }
  1755. .disabled {
  1756. filter: grayscale(0.4);
  1757. }
  1758. }
  1759. #duration {
  1760. margin: 3px;
  1761. display: flex;
  1762. align-items: center;
  1763. p {
  1764. font-size: 22px;
  1765. /** prevents duration width slightly varying and shifting other controls slightly */
  1766. width: 150px;
  1767. text-align: center;
  1768. }
  1769. }
  1770. #volume-control {
  1771. margin: 3px;
  1772. margin-top: 0;
  1773. display: flex;
  1774. align-items: center;
  1775. cursor: pointer;
  1776. .volume-slider {
  1777. width: 100%;
  1778. padding: 0 15px;
  1779. background: transparent;
  1780. min-width: 100px;
  1781. }
  1782. input[type="range"] {
  1783. -webkit-appearance: none;
  1784. margin: 7.3px 0;
  1785. }
  1786. input[type="range"]:focus {
  1787. outline: none;
  1788. }
  1789. input[type="range"]::-webkit-slider-runnable-track {
  1790. width: 100%;
  1791. height: 5.2px;
  1792. cursor: pointer;
  1793. box-shadow: 0;
  1794. background: var(--light-grey-3);
  1795. border-radius: 0;
  1796. border: 0;
  1797. }
  1798. input[type="range"]::-webkit-slider-thumb {
  1799. box-shadow: 0;
  1800. border: 0;
  1801. height: 19px;
  1802. width: 19px;
  1803. border-radius: 15px;
  1804. background: var(--primary-color);
  1805. cursor: pointer;
  1806. -webkit-appearance: none;
  1807. margin-top: -6.5px;
  1808. }
  1809. input[type="range"]::-moz-range-track {
  1810. width: 100%;
  1811. height: 5.2px;
  1812. cursor: pointer;
  1813. box-shadow: 0;
  1814. background: var(--light-grey-3);
  1815. border-radius: 0;
  1816. border: 0;
  1817. }
  1818. input[type="range"]::-moz-range-thumb {
  1819. box-shadow: 0;
  1820. border: 0;
  1821. height: 19px;
  1822. width: 19px;
  1823. border-radius: 15px;
  1824. background: var(--primary-color);
  1825. cursor: pointer;
  1826. -webkit-appearance: none;
  1827. margin-top: -6.5px;
  1828. }
  1829. input[type="range"]::-ms-track {
  1830. width: 100%;
  1831. height: 5.2px;
  1832. cursor: pointer;
  1833. box-shadow: 0;
  1834. background: var(--light-grey-3);
  1835. border-radius: 1.3px;
  1836. }
  1837. input[type="range"]::-ms-fill-lower {
  1838. background: var(--light-grey-3);
  1839. border: 0;
  1840. border-radius: 0;
  1841. box-shadow: 0;
  1842. }
  1843. input[type="range"]::-ms-fill-upper {
  1844. background: var(--light-grey-3);
  1845. border: 0;
  1846. border-radius: 0;
  1847. box-shadow: 0;
  1848. }
  1849. input[type="range"]::-ms-thumb {
  1850. box-shadow: 0;
  1851. border: 0;
  1852. height: 15px;
  1853. width: 15px;
  1854. border-radius: 15px;
  1855. background: var(--primary-color);
  1856. cursor: pointer;
  1857. -webkit-appearance: none;
  1858. margin-top: 1.5px;
  1859. }
  1860. }
  1861. #right-buttons {
  1862. display: flex;
  1863. #dropdown-toggle {
  1864. width: 35px;
  1865. }
  1866. #dislike-song,
  1867. #add-song-to-playlist .button:not(#dropdown-toggle) {
  1868. margin-left: 5px;
  1869. }
  1870. #ratings {
  1871. display: flex;
  1872. &.liked #dislike-song,
  1873. &.disliked #like-song {
  1874. background-color: var(--grey) !important;
  1875. }
  1876. #like-song.disabled,
  1877. #dislike-song.disabled {
  1878. filter: grayscale(0.4);
  1879. }
  1880. }
  1881. #add-song-to-playlist {
  1882. display: flex;
  1883. flex-direction: column-reverse;
  1884. .control {
  1885. width: fit-content;
  1886. margin-bottom: 0 !important;
  1887. button.disabled {
  1888. filter: grayscale(0.4);
  1889. border-radius: 3px;
  1890. &::after {
  1891. margin-right: 100%;
  1892. }
  1893. }
  1894. }
  1895. }
  1896. }
  1897. }
  1898. }
  1899. #sidebar-container {
  1900. border-top: 0;
  1901. position: relative;
  1902. height: inherit;
  1903. }
  1904. }
  1905. }
  1906. .footer {
  1907. margin-top: 30px;
  1908. }
  1909. /deep/ .nothing-here-text {
  1910. display: flex;
  1911. align-items: center;
  1912. justify-content: center;
  1913. }
  1914. @media (max-width: 950px) {
  1915. #mobile-progress-animation {
  1916. display: block;
  1917. }
  1918. #page-loader-container {
  1919. display: none;
  1920. }
  1921. #station-outer-container {
  1922. padding: 10px;
  1923. height: unset;
  1924. max-width: 700px;
  1925. #station-inner-container {
  1926. flex-direction: column;
  1927. #station-left-column {
  1928. #current-next-row {
  1929. flex-direction: column;
  1930. }
  1931. #control-bar-container {
  1932. #duration,
  1933. #volume-control,
  1934. #right-buttons,
  1935. #left-buttons {
  1936. margin-bottom: 5px;
  1937. justify-content: center;
  1938. }
  1939. #duration {
  1940. order: 1;
  1941. }
  1942. #volume-control {
  1943. order: 2;
  1944. max-width: 400px;
  1945. }
  1946. #right-buttons {
  1947. order: 3;
  1948. flex-wrap: wrap;
  1949. #ratings {
  1950. flex-wrap: wrap;
  1951. }
  1952. }
  1953. #left-buttons {
  1954. order: 4;
  1955. flex-wrap: wrap;
  1956. }
  1957. }
  1958. }
  1959. #station-right-column {
  1960. max-width: unset;
  1961. #about-station-container #admin-buttons {
  1962. flex-wrap: wrap;
  1963. }
  1964. #sidebar-container {
  1965. min-height: 350px;
  1966. }
  1967. }
  1968. }
  1969. }
  1970. }
  1971. </style>