Station.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  1. <template>
  2. <station-header></station-header>
  3. <div class="station">
  4. <div class="row">
  5. <div class="col-md-8 col-md-push-2 col-sm-10 col-sm-push-1 col-xs-12 video-col">
  6. <div class="video-container">
  7. <div id="player"></div>
  8. <!--iframe id="player" frameborder="0" allowfullscreen="1" title="YouTube video player" width="480" height="270" src="https://www.youtube.com/embed/xo1VInw-SKc?controls=0&amp;iv_load_policy=3&amp;rel=0&amp;showinfo=0&amp;enablejsapi=1&amp;origin=https%3A%2F%2Fmusare.com&amp;widgetid=1" kwframeid="1"></iframe-->
  9. </div>
  10. </div>
  11. <div class="col-md-8 col-md-push-2 col-sm-10 col-sm-push-1 col-xs-12">
  12. <div class="row">
  13. <button v-if="paused" @click="unpauseStation()">Unpause</button>
  14. <button v-if="!paused" @click="pauseStation()">Pause</button>
  15. <div class="col-md-8 col-sm-12 col-sm-12">
  16. <h4 id="time-display">{{timeElapsed}} / {{songDuration}}</h4>
  17. <h3>{{title}}</h3>
  18. <h4 class="thin" style="margin-left: 0">{{artists}}</h4>
  19. <div class="row">
  20. <form style="margin-top: 12px; margin-bottom: 0;" action="#" class="col-md-4 col-lg-4 col-xs-4 col-sm-4">
  21. <p style="margin-top: 0; position: relative;">
  22. <input type="range" id="volumeSlider" min="0" max="100" class="active" v-on:change="changeVolume()" v-on:input="changeVolume()">
  23. </p>
  24. </form>
  25. <div class="col-xs-8 col-sm-5 col-md-5" style="float: right;">
  26. <ul id="ratings">
  27. <li id="like" class="right"><span class="flow-text">{{likes}} </span> <i id="thumbs_up" class="material-icons grey-text" @click="toggleLike()">thumb_up</i></li>
  28. <li style="margin-right: 10px;" id="dislike" class="right"><span class="flow-text">{{dislikes}} </span><i id="thumbs_down" class="material-icons grey-text" @click="toggleDislike()">thumb_down</i></li>
  29. </ul>
  30. </div>
  31. </div>
  32. <div class="seeker-bar-container white" id="preview-progress">
  33. <div class="seeker-bar light-blue" style="width: 60.9869%;"></div>
  34. </div>
  35. </div>
  36. <img alt="Not loading" class="img-responsive col-md-4 col-xs-12 col-sm-12" onerror="this.src='../assets/notes.png'" id="song-image" style="margin-top: 10px !important" v-bind:src="image" />
  37. </div>
  38. </div>
  39. </div>
  40. </div>
  41. <main-footer></main-footer>
  42. <div class="modal fade" id="queue" tabindex="-1" role="dialog" aria-labelledby="queue-modal">
  43. <div class="modal-dialog modal-large" role="document">
  44. <div class="modal-content">
  45. <div class="modal-header">
  46. <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
  47. <h5 class="modal-title">Add to Musare</h5>
  48. </div>
  49. <div class="modal-body">
  50. <input class="form-control" type="text" placeholder="YouTube Query / Video ID / Video link / Playlist link" v-model="queueQuery"/>
  51. <button type="button" class="btn btn-primary" @click="submitQueueQuery()">Search</button>
  52. <button type="button" class="btn btn-error" @click="clearQueueQuery()" v-if="queueQueryActive">Clear List</button>
  53. <div v-if="queueQueryActive">
  54. <h2>Queue Results</h2>
  55. <div v-for="item in queueQueryResults">
  56. <h5>{{item.title}}</h5>
  57. <button @click='addItemToItems(item.id)'>Add</button>
  58. <br>
  59. </div>
  60. </div>
  61. <hr>
  62. <div class="row">
  63. <h2>Items to add</h2>
  64. <div v-for="item in queueItems">
  65. <h5>{{item.title}}</h5>
  66. <br>
  67. </div>
  68. </div>
  69. </div>
  70. <div class="modal-footer">
  71. <button type="button" class="btn btn-primary left" data-dismiss="modal" @click="addItemsToQueue()">Add items to queue</button>
  72. </div>
  73. </div>
  74. </div>
  75. </div>
  76. </template>
  77. <script>
  78. import StationHeader from '../StationHeader.vue'
  79. import MainFooter from '../MainFooter.vue'
  80. export default {
  81. data() {
  82. return {
  83. playerReady: false,
  84. currentSong: undefined,
  85. player: undefined,
  86. timePaused: 0,
  87. paused: false,
  88. songDuration: "0:00",
  89. timeElapsed: "0:00",
  90. artists: "",
  91. title: "",
  92. image: "",
  93. likes: 0,
  94. dislikes: 0,
  95. interval: 0,
  96. queueQuery: "",
  97. queueQueryActive: false,
  98. queueQueryResults: [],
  99. queueItems: []
  100. }
  101. },
  102. methods: {
  103. youtubeReady: function() {
  104. let local = this;
  105. console.log("YT Ready!!!");
  106. local.player = new YT.Player("player", {
  107. height: 270,
  108. width: 480,
  109. videoId: local.currentSong.id,
  110. playerVars: {controls: 1, iv_load_policy: 3, rel: 0, showinfo: 0},
  111. events: {
  112. 'onReady': function (event) {
  113. console.log("Ready!!!");
  114. local.playerReady = true;
  115. let volume = parseInt(localStorage.getItem("volume"));
  116. volume = (typeof volume === "number") ? volume : 20;
  117. local.player.setVolume(volume);
  118. if (volume > 0) {
  119. local.player.unMute();
  120. }
  121. local.playVideo();
  122. },
  123. 'onStateChange': function (event) {
  124. if (event.data === 1 && local.videoLoading === true) {
  125. local.videoLoading = false;
  126. local.player.seekTo(local.getTimeElapsed() / 1000, true);
  127. if (local.paused) {
  128. local.player.pauseVideo();
  129. }
  130. }
  131. }
  132. }
  133. });
  134. },
  135. startSong: function(song) {
  136. let local = this;
  137. if (local.playerReady) {
  138. }
  139. },
  140. getTimeElapsed: function() {
  141. let local = this;
  142. if (local.currentSong !== undefined) {
  143. return Date.now() - local.currentSong.startedAt - local.timePaused;
  144. }
  145. return 0;
  146. },
  147. pauseVideo: function() {
  148. let local = this;
  149. local.paused = true;
  150. if (local.playerReady) {
  151. local.player.pauseVideo();
  152. }
  153. },
  154. unpauseVideo: function() {
  155. let local = this;
  156. local.paused = false;
  157. if (local.playerReady) {
  158. local.player.seekTo(local.getTimeElapsed() / 1000);
  159. local.player.playVideo();
  160. }
  161. },
  162. playVideo: function() {
  163. let local = this;
  164. if (local.playerReady) {
  165. local.videoLoading = true;
  166. local.player.loadVideoById(local.currentSong.id);
  167. var d = moment.duration(parseInt(local.currentSong.duration), 'seconds');
  168. local.songDuration = d.minutes() + ":" + ("0" + d.seconds()).slice(-2);
  169. local.artists = local.currentSong.artists.join(", ");
  170. local.title = local.currentSong.title;
  171. local.image = local.currentSong.image;
  172. local.likes = local.currentSong.likes;
  173. local.dislikes = local.currentSong.dislikes;
  174. if (local.interval !== 0) {
  175. clearInterval(local.interval);
  176. }
  177. local.interval = setInterval(function () {
  178. local.resizeSeekerbar();
  179. local.calculateTimeElapsed();
  180. }, 250);
  181. }
  182. },
  183. resizeSeekerbar: function() {
  184. let local = this;
  185. if (!local.paused) {
  186. $(".seeker-bar").width(((local.getTimeElapsed() / 1000) / local.currentSong.duration * 100) + "%");
  187. }
  188. },
  189. calculateTimeElapsed: function() {
  190. let local = this;
  191. let currentTime = Date.now();
  192. if (local.timePausedCurrentTime !== undefined && local.paused) {
  193. local.timePaused += (Date.now() - local.timePausedCurrentTime);
  194. local.timePausedCurrentTime = undefined;
  195. }
  196. let duration = (Date.now() - local.currentSong.startedAt - local.timePaused) / 1000;
  197. let songDuration = local.currentSong.duration;
  198. if (songDuration <= duration) {
  199. local.player.pauseVideo();
  200. }
  201. let d = moment.duration(duration, 'seconds');
  202. console.log(duration, " ", local.timePaused);
  203. if ((!local.paused || local.timeElapsed === "0:00") && duration <= songDuration) {
  204. local.timeElapsed = d.minutes() + ":" + ("0" + d.seconds()).slice(-2);
  205. }
  206. },
  207. changeVolume: function() {
  208. let local = this;
  209. let volume = $("#volumeSlider").val();
  210. localStorage.setItem("volume", volume);
  211. if (local.playerReady) {
  212. local.player.setVolume(volume);
  213. if (volume > 0) {
  214. local.player.unMute();
  215. }
  216. }
  217. },
  218. unpauseStation: function() {
  219. console.log("UNPAUSE1");
  220. let local = this;
  221. local.stationSocket.emit("unpause");
  222. },
  223. pauseStation: function() {
  224. console.log("PAUSE1");
  225. let local = this;
  226. local.stationSocket.emit("pause");
  227. },
  228. toggleLike: function() {
  229. /*let local = this;
  230. local.stationSocket.emit("toggleLike");//TODO Add code here to see if this was a success or not*/
  231. },
  232. toggleDislike: function() {
  233. /*let local = this;
  234. local.stationSocket.emit("toggleDislike");//TODO Add code here to see if this was a success or not*/
  235. },
  236. addItemToItems: function(id) {
  237. let local = this;
  238. let ids = local.queueItems.map(function(item) {
  239. return item.id;
  240. });
  241. let item;
  242. local.queueQueryResults.forEach(function(result) {
  243. if (result.id === id) {
  244. console.log(result);
  245. item = result;
  246. }
  247. });
  248. if (ids.indexOf(id) === -1) {
  249. console.log(item, 222);
  250. local.queueItems.push(item);
  251. local.queueQuery = "";
  252. local.queueQueryActive = false;
  253. local.queueQueryResults = [];
  254. } else {
  255. //TODO Error
  256. }
  257. },
  258. addItemsToQueue: function() {
  259. let local = this;
  260. let items = local.queueItems;
  261. local.socket.emit("/songs/queue/addSongs/:songs", items, function(data) {
  262. console.log(data);
  263. if (!data.err) {
  264. local.queueItems = [];
  265. $('#queue').modal('hide');
  266. }
  267. });
  268. },
  269. submitQueueQuery: function() {
  270. let local = this;
  271. let query = local.queueQuery;
  272. local.socket.emit("/youtube/getVideos/:query", query, function(data) {
  273. if (!data.err) {
  274. /*queueQueryActive:
  275. queueQueryResults:*/
  276. if (data.type === "playlist") {
  277. let added = 0;
  278. let duplicate = 0;
  279. let items = [];
  280. let ids = local.queueItems.map(function(item) {
  281. return item.id;
  282. });
  283. data.items.forEach(function(item) {
  284. if (ids.indexOf(item.id) === -1) {
  285. items.push(item);
  286. added++;
  287. } else {
  288. duplicate++;
  289. }
  290. });
  291. //TODO Give result
  292. local.queueItems = local.queueItems.concat(items);
  293. } else if (data.type === "video") {
  294. let ids = local.queueItems.map(function(item) {
  295. return item.id;
  296. });
  297. if (data.item !== undefined) {
  298. if (ids.indexOf(data.item.id)) {
  299. local.queueItems.push(data.item);
  300. }
  301. }
  302. //TODO Give result
  303. } else {
  304. local.queueQueryResults = [];
  305. data.items.forEach(function(item) {
  306. local.queueQueryResults.push(item);
  307. });
  308. //TODO Give result
  309. local.queueQueryActive = true;
  310. }
  311. }
  312. });
  313. }
  314. },
  315. ready: function() {
  316. let local = this;
  317. window.onYouTubeIframeAPIReady = function() {
  318. console.log("API READY?");
  319. local.youtubeReady();
  320. };
  321. local.socket = this.$parent.socket;
  322. local.stationSocket = io.connect('http://dev.musare.com/edm');
  323. local.stationSocket.on("connected", function(data) {
  324. console.log("JOINED!?");
  325. local.currentSong = data.currentSong;
  326. local.paused = data.paused;
  327. local.timePaused = data.timePaused;
  328. local.timePausedCurrentTime = data.currentTime;
  329. let tag = document.createElement('script');
  330. tag.src = "https://www.youtube.com/iframe_api";
  331. let firstScriptTag = document.getElementsByTagName('script')[0];
  332. firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
  333. });
  334. local.stationSocket.on("skippedSong", function(currentSong) {
  335. console.log("SKIPPED SONG");
  336. local.currentSong = currentSong;
  337. local.timePaused = 0;
  338. local.playVideo();
  339. });
  340. local.stationSocket.on("pause", function() {
  341. console.log("PAUSE");
  342. local.pauseVideo();
  343. });
  344. local.stationSocket.on("unpause", function(timePaused) {
  345. console.log("UNPAUSE");
  346. local.timePaused = timePaused;
  347. local.unpauseVideo();
  348. });
  349. let volume = parseInt(localStorage.getItem("volume"));
  350. volume = (typeof volume === "number") ? volume : 20;
  351. $("#volumeSlider").val(volume);
  352. // TODO: Remove this
  353. /*local.socket.emit("/station/:id/join", "edm", function(data) {
  354. console.log("JOINED!?");
  355. local.currentSong = data.data.currentSong;
  356. local.paused = data.data.paused;
  357. local.timePaused = data.data.timePaused;
  358. local.timePausedCurrentTime = data.data.currentTime;
  359. let tag = document.createElement('script');
  360. tag.src = "https://www.youtube.com/iframe_api";
  361. let firstScriptTag = document.getElementsByTagName('script')[0];
  362. firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
  363. });*/
  364. },
  365. components: { StationHeader, MainFooter }
  366. }
  367. </script>
  368. <style lang="sass">
  369. .modal-large {
  370. width: 75%;
  371. }
  372. .station {
  373. flex: 1 0 auto;
  374. padding-top: 4.5vw;
  375. transition: all 0.1s;
  376. margin: 0 auto;
  377. max-width: 1280px;
  378. width: 90%;
  379. @media only screen and (min-width: 993px) {
  380. width: 70%;
  381. }
  382. @media only screen and (min-width: 601px) {
  383. width: 85%;
  384. }
  385. input[type=range] {
  386. -webkit-appearance: none;
  387. width: 100%;
  388. margin: 7.3px 0;
  389. }
  390. input[type=range]:focus {
  391. outline: none;
  392. }
  393. input[type=range]::-webkit-slider-runnable-track {
  394. width: 100%;
  395. height: 5.2px;
  396. cursor: pointer;
  397. box-shadow: 0;
  398. background: #c2c0c2;
  399. border-radius: 0;
  400. border: 0;
  401. }
  402. input[type=range]::-webkit-slider-thumb {
  403. box-shadow: 0;
  404. border: 0;
  405. height: 19px;
  406. width: 19px;
  407. border-radius: 15px;
  408. background: #03a9f4;
  409. cursor: pointer;
  410. -webkit-appearance: none;
  411. margin-top: -6.5px;
  412. }
  413. input[type=range]::-moz-range-track {
  414. width: 100%;
  415. height: 5.2px;
  416. cursor: pointer;
  417. box-shadow: 0;
  418. background: #c2c0c2;
  419. border-radius: 0;
  420. border: 0;
  421. }
  422. input[type=range]::-moz-range-thumb {
  423. box-shadow: 0;
  424. border: 0;
  425. height: 19px;
  426. width: 19px;
  427. border-radius: 15px;
  428. background: #03a9f4;
  429. cursor: pointer;
  430. -webkit-appearance: none;
  431. margin-top: -6.5px;
  432. }
  433. input[type=range]::-ms-track {
  434. width: 100%;
  435. height: 5.2px;
  436. cursor: pointer;
  437. box-shadow: 0;
  438. background: #c2c0c2;
  439. border-radius: 1.3px;
  440. }
  441. input[type=range]::-ms-fill-lower {
  442. background: #c2c0c2;
  443. border: 0;
  444. border-radius: 0;
  445. box-shadow: 0;
  446. }
  447. input[type=range]::-ms-fill-upper {
  448. background: #c2c0c2;
  449. border: 0;
  450. border-radius: 0;
  451. box-shadow: 0;
  452. }
  453. input[type=range]::-ms-thumb {
  454. box-shadow: 0;
  455. border: 0;
  456. height: 15px;
  457. width: 15px;
  458. border-radius: 15px;
  459. background: #03a9f4;
  460. cursor: pointer;
  461. -webkit-appearance: none;
  462. margin-top: 1.5px;
  463. }
  464. .video-container {
  465. position: relative;
  466. padding-bottom: 56.25%;
  467. height: 0;
  468. overflow: hidden;
  469. iframe {
  470. position: absolute;
  471. top: 0;
  472. left: 0;
  473. width: 100%;
  474. height: 100%;
  475. pointer-events: none;
  476. }
  477. }
  478. .video-col {
  479. padding-right: 0.75rem;
  480. padding-left: 0.75rem;
  481. }
  482. }
  483. .room-title {
  484. left: 50%;
  485. -webkit-transform: translateX(-50%);
  486. transform: translateX(-50%);
  487. font-size: 2.1em;
  488. }
  489. #ratings {
  490. span {
  491. font-size: 1.68rem;
  492. }
  493. i {
  494. color: #9e9e9e !important;
  495. cursor: pointer;
  496. transition: 0.1s color;
  497. }
  498. }
  499. #time-display {
  500. margin-top: 30px;
  501. float: right;
  502. }
  503. #thumbs_up:hover {
  504. color: #87D37C !important;
  505. }
  506. #thumbs_down:hover {
  507. color: #EC644B !important;
  508. }
  509. .seeker-bar-container {
  510. position: relative;
  511. height: 5px;
  512. display: block;
  513. width: 100%;
  514. overflow: hidden;
  515. margin-top: 20px;
  516. }
  517. .seeker-bar {
  518. top: 0;
  519. left: 0;
  520. bottom: 0;
  521. position: absolute;
  522. }
  523. ul {
  524. list-style: none;
  525. margin: 0;
  526. display: block;
  527. }
  528. h1, h2, h3, h4, h5, h6 {
  529. font-weight: 400;
  530. line-height: 1.1;
  531. }
  532. h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
  533. font-weight: inherit;
  534. }
  535. h1 {
  536. font-size: 4.2rem;
  537. line-height: 110%;
  538. margin: 2.1rem 0 1.68rem 0;
  539. }
  540. h2 {
  541. font-size: 3.56rem;
  542. line-height: 110%;
  543. margin: 1.78rem 0 1.424rem 0;
  544. }
  545. h3 {
  546. font-size: 2.92rem;
  547. line-height: 110%;
  548. margin: 1.46rem 0 1.168rem 0;
  549. }
  550. h4 {
  551. font-size: 2.28rem;
  552. line-height: 110%;
  553. margin: 1.14rem 0 0.912rem 0;
  554. }
  555. h5 {
  556. font-size: 1.64rem;
  557. line-height: 110%;
  558. margin: 0.82rem 0 0.656rem 0;
  559. }
  560. h6 {
  561. font-size: 1rem;
  562. line-height: 110%;
  563. margin: 0.5rem 0 0.4rem 0;
  564. }
  565. .thin {
  566. font-weight: 200;
  567. }
  568. .left {
  569. float: left !important;
  570. }
  571. .right {
  572. float: right !important;
  573. }
  574. .light-blue {
  575. background-color: #03a9f4 !important;
  576. }
  577. .white {
  578. background-color: #FFFFFF !important;
  579. }
  580. </style>