Browse Source

Added private rooms and private playlists (not finished yet).

KrisVos130 9 years ago
parent
commit
af55ad13ea

+ 102 - 0
app/client/scripts/events.js

@@ -1630,6 +1630,108 @@ Template.room.events({
         }, 10);
     }
 });
+
+Template.privateRoom.events({
+    "input #volume_slider": function() {
+        var volume = Number($("#volume_slider").val());
+        localStorage.setItem("volume", volume);
+        if (YTPlayer !== undefined) {
+            YTPlayer.setVolume(volume);
+        }
+    },
+    "click #global-chat-tab": function () {
+        $("#global-chat-tab").removeClass("unread-messages");
+    },
+    "click #sync": function () {
+        if (Session.get("currentSong") !== undefined) {
+            var room = PrivateRooms.findOne({name: Session.get("privateRoomName")});
+            if (room !== undefined) {
+                var timeIn = Date.now() - Session.get("currentSong").started - room.timePaused;
+                if (YTPlayer !== undefined) {
+                    YTPlayer.seekTo(timeIn / 1000);
+                }
+            }
+        }
+    },
+    "click #lock": function () {
+        Meteor.call("lockPrivateRoom", Session.get("privateRoomName"));
+        var $parent = $("#lock").parent();
+        $("#lock").remove();
+        $parent.append('<a id="unlock"><i class="material-icons">lock_open</i></a>')
+    },
+    "click #unlock": function () {
+        Meteor.call("unlockPrivateRoom", Session.get("privateRoomName"));
+        var $parent = $("#unlock").parent();
+        $("#unlock").remove();
+        $parent.append('<a id="lock"><i class="material-icons">lock_outline</i></a>')
+    },
+    "click #submit": function () {
+        if(Meteor.userId()){
+            sendMessageGlobal();
+            Meteor.setTimeout(function () {
+                $(".chat-ul").scrollTop(100000);
+            }, 1000)
+        } else {
+            var $toastContent = $('<span>Message not sent. You must log in</span>');
+            Materialize.toast($toastContent, 2000);
+        }
+    },
+    "keyup #chat-message": function (e) {
+        if (e.type === "keyup" && e.which === 13) {
+            if(Meteor.userId()){
+                e.preventDefault();
+                if (!$('#chat-message').data('dropdownshown')) {
+                    sendMessageGlobal();
+                    Meteor.setTimeout(function () {
+                        $(".chat-ul").scrollTop(100000);
+                    }, 1000)
+                }
+            } else {
+                var $toastContent = $('<span>Message not sent. You must log in</span>');
+                Materialize.toast($toastContent, 2000);
+            }
+        }
+    },
+    "click #vote-skip": function () {
+        Meteor.call("votePrivateSkip", Session.get("privateRoomName"), function (err, res) {
+            $("#vote-skip").addClass("disabled");
+            if(err){
+                var $toastContent = $('<span><strong>Vote not submitted</strong> ' + err.reason + '</span>');
+                Materialize.toast($toastContent, 4000);
+            }
+        });
+    },
+    "click #volume-icon": function () {
+        var volume = 0;
+        var slider = $("#volume-slider").slider();
+        $("#volume-icon").removeClass("fa-volume-down").addClass("fa-volume-off")
+        if (YTPlayer !== undefined) {
+            YTPlayer.setVolume(volume);
+            localStorage.setItem("volume", volume);
+            $("#volume-slider").slider("setValue", volume);
+        }
+    },
+    "click #play": function () {
+        Meteor.call("resumePrivateRoom", Session.get("privateRoomName"));
+        var $parent = $("#play").parent();
+        $("#play").remove();
+        $parent.append('<a id="pause"><i class="material-icons">pause</i></a>')
+    },
+    "click #pause": function () {
+        Meteor.call("pausePrivateRoom", Session.get("privateRoomName"));
+        var $parent = $("#pause").parent();
+        $("#pause").remove();
+        $parent.append('<a id="play"><i class="material-icons">play_arrow</i></a>')
+    },
+    "click #skip": function () {
+        Meteor.call("skipPrivateSong", Session.get("privateRoomName"));
+    },
+    "click #admin-dropdown a": function(){
+        Meteor.setTimeout(function(){
+            $(".dropdown-button").click();
+        }, 10);
+    }
+});
 // Settings Template
 Template.settings.events({
     "change #showRating": function() {

+ 51 - 0
app/client/scripts/helpers.js

@@ -520,6 +520,57 @@ Template.room.helpers({
     }
 });
 
+Template.privateRoom.helpers({
+    globalChat: function () {
+        Meteor.setTimeout(function () {
+            var elem = document.getElementById('global-chat');
+            if (elem !== undefined && elem !== null) {
+                elem.scrollTop = elem.scrollHeight;
+            }
+        }, 100);
+        return Chat.find({type: "global"}, {sort: {time: -1}, limit: 50}).fetch().reverse();
+    },
+    privateRoomDisplayName: function () {
+        var parts = location.href.split('/');
+        var id = parts.pop().toLowerCase();
+        return PrivateRooms.findOne({name: id}).displayName;
+    },
+    users: function () {
+        var parts = location.href.split('/');
+        var id = parts.pop().toLowerCase();
+        return PrivateRooms.findOne({name: id}).users;
+    },
+    title: function () {
+        return Session.get("title");
+    },
+    loaded: function () {
+        return Session.get("loaded");
+    },
+    paused: function () {
+        return Session.get("state") === "paused";
+    },
+    private: function () {
+        return 1;
+        //return Rooms.findOne({type: Session.get("type")}).private === true;
+    },
+    currentSong: function(){
+        return Session.get("currentSong");
+    },
+    votes: function () {
+        return PrivateRooms.findOne({name: Session.get("privateRoomName")}).votes;
+    },
+    usersInRoom: function(){
+        var userList = [];
+        var roomUserList = PrivateRooms.findOne({type: Session.get("privateRoomName")}).userList;
+        roomUserList.forEach(function(user){
+            if(userList.indexOf(user) === -1){
+                userList.push(user);
+            }
+        })
+        return userList;
+    }
+});
+
 Template.settings.helpers({
     username: function () {
         if (Meteor.user() !== undefined) {

+ 220 - 0
app/client/scripts/onCreated.js

@@ -373,6 +373,226 @@ Template.room.onCreated(function () {
     }, 1000);
 });
 
+Template.privateRoom.onCreated(function () {
+    Chat.after.find(function(userId, selector) {
+        if (selector.type === "global") {
+            if (!$("#global-chat-tab").hasClass("active")) {
+                $("#global-chat-tab").addClass("unread-messages");
+            }
+        }
+    });
+
+    var parts = location.href.split('/');
+    var id = parts.pop();
+    var name = id.toLowerCase();
+    if (resizeSeekerbarInterval !== undefined) {
+        Meteor.clearInterval(resizeSeekerbarInterval);
+        resizeSeekerbarInterval = undefined;
+    }
+    YTPlayer = undefined;
+    Session.set("videoHidden", false);
+    var tag = document.createElement("script");
+    tag.src = "https://www.youtube.com/iframe_api";
+    var firstScriptTag = document.getElementsByTagName('script')[0];
+    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
+
+    Session.set("singleVideo", true);
+
+    var currentSong = undefined;
+    var currentSongR = undefined;
+
+    function getTimeElapsed() {
+        var name = Session.get("privateRoomName");
+        if (currentSong !== undefined) {
+            var room = PrivateRooms.findOne({name: name});
+            if (room !== undefined) {
+                return Date.now() - currentSong.started - room.timePaused;
+            }
+        }
+        return 0;
+    }
+
+    function getSongInfo(songData){
+        Session.set("title", songData.title);
+        Session.set("id", songData.id);
+        Session.set("duration", parseInt(songData.duration));
+        var d = moment.duration(parseInt(songData.duration), 'seconds');
+        $("#time-total").text(d.minutes() + ":" + ("0" + d.seconds()).slice(-2));
+        Session.set("timeFormat", d.minutes() + ":" + ("0" + d.seconds()).slice(-2));
+        document.title = Session.get("title") + " - Musare";
+    }
+
+    function resizeSeekerbar() {
+        if (Session.get("state") === "playing") {
+            $(".seeker-bar").width(((getTimeElapsed() / 1000) / Session.get("duration") * 100) + "%");
+        }
+    }
+
+    function startSong() {
+        $("#time-elapsed").text("0:00");
+        $("#vote-skip").attr("disabled", false);
+        if (currentSong !== undefined) {
+            if (YTPlayer !== undefined && YTPlayer.stopVideo !== undefined) YTPlayer.stopVideo();
+
+            var volume = localStorage.getItem("volume") || 20;
+            $("#volume_slider").val(volume);
+
+            $("#player").show();
+            function loadVideo() {
+                if (!Session.get("YTLoaded")) {
+                    Session.set("loadVideoTimeout", Meteor.setTimeout(function () {
+                        loadVideo();
+                    }, 500));
+                } else {
+                    if (YTPlayer === undefined) {
+                        YTPlayer = new YT.Player("player", {
+                            height: 270,
+                            width: 480,
+                            videoId: currentSong.id,
+                            playerVars: {controls: 0, iv_load_policy: 3, rel: 0, showinfo: 0},
+                            events: {
+                                'onReady': function (event) {
+                                    event.target.seekTo(getTimeElapsed() / 1000);
+                                    event.target.playVideo();
+                                    event.target.setVolume(volume);
+                                    resizeSeekerbar();
+                                },
+                                'onStateChange': function (event) {
+                                    if (Session.get("YTLoaded")) {
+                                        if (event.data == YT.PlayerState.PAUSED && Session.get("state") === "playing") {
+                                            event.target.seekTo(getTimeElapsed() / 1000);
+                                            event.target.playVideo();
+                                        }
+                                        if (event.data == YT.PlayerState.PLAYING && Session.get("state") === "paused") {
+                                            event.target.seekTo(getTimeElapsed() / 1000);
+                                            event.target.pauseVideo();
+                                        }
+                                    }
+                                }
+                            }
+                        });
+                    } else {
+                        YTPlayer.loadVideoById(currentSong.id);
+                        YTPlayer.seekTo(getTimeElapsed() / 1000);
+                        $("#vote-skip").removeClass("disabled");
+                    }
+                    Session.set("pauseVideo", false);
+                    getSongInfo(currentSong);
+                }
+            }
+            loadVideo();
+        }
+    }
+
+    Session.set("loaded", false);
+    Meteor.subscribe("private_rooms", function() {
+        var parts = location.href.split('/');
+        var id = parts.pop();
+        var name = id.toLowerCase();
+        Session.set("privateRoomName", name);
+        if (PrivateRooms.find({name: name}).count() !== 1) {
+            window.location = "/";
+        } else {
+            StationSubscription = Meteor.subscribe("pr_" + name);
+            Session.set("loaded", true);
+            Session.set("minterval", Meteor.setInterval(function () {
+                var room = PrivateRooms.findOne({name: name});
+                if (room !== undefined) {
+                    if (room.state === "paused" || Session.get("pauseVideo")) {
+                        Session.set("state", "paused");
+                        // TODO Fix issue where sometimes nothing loads with the YT is not defined error. The error points to around this.
+                        if (YTPlayer !== undefined && YTPlayer.getPlayerState !== undefined && YTPlayer.getPlayerState() === 1) {
+                            YTPlayer.pauseVideo();
+                        }
+                    } else {
+                        Session.set("state", "playing");
+                        if (YTPlayer !== undefined && YTPlayer.getPlayerState !== undefined && YTPlayer.getPlayerState() !== 1) {
+                            YTPlayer.playVideo();
+                        }
+                    }
+                }
+
+                if (currentSongR === undefined || room.currentSong.started !== currentSongR.started) {
+                    Session.set("previousSong", currentSong);
+                    currentSongR = room.currentSong;
+
+                    currentSong = room.currentSong.song;
+                    currentSong.started = room.currentSong.started;
+                    Session.set("currentSong", currentSong);
+                    Meteor.clearTimeout(Session.get("loadVideoTimeout"));
+                    startSong();
+                }
+
+                if (currentSong !== undefined) {
+                    if (room !== undefined) {
+                        var duration = (Date.now() - currentSong.started - room.timePaused) / 1000;
+                        var song_duration = currentSong.duration;
+                        if (song_duration <= duration) {
+                            Session.set("pauseVideo", true);
+                        }
+                        var d = moment.duration(duration, 'seconds');
+                        if (Session.get("state") === "playing") {
+                            $("#time-elapsed").text(d.minutes() + ":" + ("0" + d.seconds()).slice(-2));
+                        }
+                    }
+                }
+            }, 100));
+            resizeSeekerbarInterval = Meteor.setInterval(function () {
+                resizeSeekerbar();
+            }, 500)
+        }
+    });
+    Meteor.setTimeout(function(){
+        $("#playlist-slideout").on("click", function(){
+            if($("#chat-slide-out").css("right") === "0px"){
+                $("#chat-slideout").sideNav("hide");
+            }
+            else if($("#users-slide-out").css("right") === "0px"){
+                $("#users-slideout").sideNav("hide");
+            }
+            var marginRightWidth = ($(document).width() - $(".container").width()) / 2 + "px";
+            $(".room-container").css("margin-right", "370px")
+            if($("#playlist-slide-out").css("right") === "0px"){
+                $(".room-container").css("margin-right", marginRightWidth);
+            }
+        });
+        $("#chat-slideout").on("click", function(){
+            if($("#playlist-slide-out").css("right") === "0px"){
+                $("#playlist-slideout").sideNav("hide");
+            }
+            else if($("#users-slide-out").css("right") === "0px"){
+                $("#users-slideout").sideNav("hide");
+            }
+            var marginRightWidth = ($(document).width() - $(".container").width()) / 2 + "px";
+            $(".chat-ul").scrollTop(1000000);
+            $(".room-container").css("margin-right", "370px")
+            if($("#chat-slide-out").css("right") === "0px"){
+                $(".room-container").css("margin-right", marginRightWidth);
+            }
+        });
+        $("#users-slideout").on("click", function(){
+            if($("#playlist-slide-out").css("right") === "0px"){
+                $("#playlist-slideout").sideNav("hide");
+            }
+            else if($("#chat-slide-out").css("right") === "0px"){
+                $("#chat-slideout").sideNav("hide");
+            }
+            var marginRightWidth = ($(document).width() - $(".container").width()) / 2 + "px";
+            $(".room-container").css("margin-right", "370px")
+            if($("#users-slide-out").css("right") === "0px"){
+                $(".room-container").css("margin-right", marginRightWidth);
+            }
+        });
+        $("body").on("click", function(e){
+
+        });
+        $(window).on("resize", function(){
+            var marginRightWidth = ($(document).width() - $(".container").width()) / 2 + "px";
+            $(".container").css("margin-right", marginRightWidth);
+        })
+    }, 1000);
+});
+
 Template.settings.onCreated(function() {
     $(document).ready(function() {
         var user = Meteor.user();

+ 13 - 0
app/client/scripts/onRendered.js

@@ -94,3 +94,16 @@ Template.room.onRendered(function() {
         $("#volume_slider").val(volume);
     }, 1000)
 });
+
+Template.privateRoom.onRendered(function() {
+    if (Session.get("rTimeInterval") !== undefined) {
+        Meteor.clearInterval(Session.get("rTimeInterval"))
+    }
+    Session.set("rTimeInterval", Meteor.setInterval(function() {
+        Session.set("time", new Date().getTime());
+    }, 10000));
+    window.setTimeout(function(){
+        var volume = (localStorage.getItem("volume") !== undefined) ? localStorage.getItem("volume") : 20;
+        $("#volume_slider").val(volume);
+    }, 1000)
+});

+ 3 - 3
app/client/scripts/routes.js

@@ -185,13 +185,13 @@ Router.route("/u/:user", {
     name: "profile"
 });
 
-Router.route("/private/:type", {
+Router.route("/private/:name", {
     waitOn: function() {
-        return [Meteor.subscribe("isModerator", Meteor.userId()), Meteor.subscribe("isAdmin", Meteor.userId()), Meteor.subscribe("rooms")];
+        return [Meteor.subscribe("isModerator", Meteor.userId()), Meteor.subscribe("isAdmin", Meteor.userId()), Meteor.subscribe("private_rooms")];
     },
     action: function() {
         var user = Meteor.users.findOne({});
-        var room = Rooms.findOne({type: this.params.type});
+        var room = PrivateRooms.findOne({name: this.params.name});
         if (room !== undefined) {
             if ((room.private === true && user !== undefined && user.profile !== undefined && (user.profile.rank === "admin" ||
                 user.profile.rank === "moderator")) || room.private === false || (user !== undefined && user.profile !== undefined && room.allowed.includes(user.profile))) {

+ 2 - 412
app/client/templates/privateRoom.html

@@ -11,7 +11,7 @@
                         <li><a class='dropdown-button' data-activates='admin-dropdown'><i class="material-icons">control_point</i></a></li>
                     {{/if}}
                 </ul>
-                <span class="brand-logo center">{{type}}</span>
+                <span class="brand-logo center">{{privateRoomDisplayName}}</span>
                 <ul class="right hide-on-med-and-down">
                     <li><a href="#" data-position="bottom" data-delay="50" data-tooltip="Playlist" id="playlist-slideout" data-activates="playlist-slide-out" class="tooltipped header-collapse"><i class="material-icons">queue_music</i></a></li>
                     <li><a href="#" data-position="bottom" data-delay="50" data-tooltip="Chat" id="chat-slideout" data-activates="chat-slide-out" class="tooltipped header-collapse"><i class="material-icons">chat</i></a></li>
@@ -34,27 +34,17 @@
                         <div class="col s12 m12 l8">
                             <h4 id="time-display"><span id="time-elapsed"></span> / <span id="time-total"></span></h4>
                             <h3>{{{title}}}</h3>
-                            <h4 class="thin" style="margin-left: 0">{{{artist}}}</h4>
                             <div class="row">
                                 <form action="#" class="left col s4 m4 l4">
                                     <p class="range-field" style="margin-top: 0">
                                         <input type="range" id="volume_slider" min="0" max="100"/>
                                     </p>
                                 </form>
-                                <div class="right col s8 m5 l5">
-                                    <ul>
-                                        <li id="like" class="right"><span class="flow-text">{{likes}} </span> <i id="thumbs_up" class="material-icons grey-text {{liked}}">thumb_up</i></li>
-                                        <li style="margin-right: 10px;" id="dislike" class="right"><span class="flow-text">{{dislikes}} </span><i id="thumbs_down" class="material-icons grey-text {{disliked}}">thumb_down</i></li>
-                                    </ul>
-                                </div>
                             </div>
                             <div class="seeker-bar-container white" id="preview-progress">
                                 <div class="seeker-bar teal" style="width: 0%"></div>
                             </div>
                         </div>
-                        <img alt="Not loading" class="responsive-img song-img col s12 m12 l4"
-                             onError="this.src='http://static.boredpanda.com/blog/wp-content/uploads/2014/04/amazing-fox-photos-182.jpg'"
-                             id="song-img" style="margin-top: 10px !important"/>
                     </div>
                 </div>
             </div>
@@ -88,7 +78,7 @@
     <!--Playlist slideout-->
     <div id="playlist-slide-out" class="side-nav room-slideout">
         <h5>Playlist</h5>
-        {{> playlist}}
+
     </div>
     <div id="users-slide-out" class="side-nav room-slideout">
         <h5>Users In Room</h5>
@@ -105,407 +95,7 @@
         <li><a id="shuffle"><i class="material-icons">shuffle</i></a></li>
         <li><a id="lock"><i class="material-icons">lock_outline</i></a></li>
     </ul>
-    <!--Add song modal-->
-    <div id="add_song_modal" class="modal">
-        <div class="modal-content container">
-            <div class="row">
-                <form class="black-text" id="search-info">
-                    <div class="row">
-                        <div class="input-field">
-                            <select id="si_or_pl">
-                                <option value="singleVideo" selected>Single Song</option>
-                                <option value="importPlaylist">Import Playlist</option>
-                                <label>Import Type</label>
-                            </select>
-                        </div>
-                    </div>
-                    {{#if singleVideo}}
-                        {{#if editingSong}}
-                            <div class="row">
-                                <button type="button" id="return-button" style="margin-bottom: 20px;"
-                                        class="btn btn-small col l4 m4 s8 offset-l4 offset-m4 offset-s2 center waves-effect waves-light">
-                                    Return
-                                </button>
-                                <h4 class="center-align col l12 m12 s12">Add Song</h4>
-                                <div class="input-field col l8 m8 s12 offset-l2 offset-m2">
-                                    <select multiple id="genres">
-                                        <option value="" disabled selected>Select Genre(s):</option>
-                                        <option value="edm">EDM</option>
-                                        <option value="chill">Chill</option>
-                                        <option value="pop">Pop</option>
-                                        <option value="country">Country</option>
-                                        <option value="rock">Rock</option>
-                                        <option value="randb">R&B</option>
-                                        <option value="rap">Rap</option>
-                                        <option value="heavymetal">Heavy Metal</option>
-                                        <option value="christmas">Christmas</option>
-                                        <option value="alternative">Alternative</option>
-                                    </select>
-                                    <label class="white-text">Genre(s)</label>
-                                </div>
-                                <div class="input-field col l8 m8 s12 offset-l2 offset-m2">
-                                    <i class="material-icons prefix">vpn_key</i>
-                                    <label for="id" class="teal-text">Song ID</label>
-                                    <input class="validate" name="id" id="id" type="text" pattern=".{11}"/>
-                                </div>
-                                <div class="input-field col l8 m8 s12 offset-l2 offset-m2">
-                                    <i class="material-icons prefix">person</i>
-                                    <label for="artist" class="teal-text">Song Artist</label>
-                                    <input class="validate" name="artist" id="artist" aria-required="true"
-                                           type="text"/>
-                                </div>
-                                <div class="input-field col l8 m8 s12 offset-l2 offset-m2">
-                                    <i class="material-icons prefix">subject</i>
-                                    <label for="title" class="teal-text">Song Title</label>
-                                    <input class="validate required" name="title" id="title" type="text"/>
-                                </div>
-                                <button type="button" id="add-song-button"
-                                        class="btn btn-large col l6 m6 s10 offset-l3 offset-m3 offset-s1 waves-effect waves-light">
-                                    Add Song
-                                </button>
-                            </div>
-                            <script>
-                                $('#genres').material_select();
-                            </script>
-                        {{else}}
-                            <div class="row" id="single-video">
-                                <div class="input-field">
-                                    <input id="song-input" type="text" class="validate">
-                                    <label for="search_for_song">Search for song</label>
-                                </div>
-                                <a class="waves-effect waves-light btn" id="search-song"><i
-                                        class="material-icons left">search</i>Search</a>
-                                {{#if singleVideoResultsActive}}
-                                    <div id="single-video-results">
-                                        <div style="overflow: auto; height: 30vh; margin-top: 1rem;">
-                                            <ul class="collection teal-text">
-                                                {{#each result in singleVideoResults}}
-                                                    <li class="collection-item avatar youtube-search-result-li">
-                                                        <img src="{{result.image}}"
-                                                             onerror="this.src='http://static.boredpanda.com/blog/wp-content/uploads/2014/04/amazing-fox-photos-182.jpg'"
-                                                             alt="" class="video-import-thumbnail">
-                                                        <span class="title video-import-text">{{result.title}}</span>
-                                                        <p class="video-import-text">{{result.artist}} <br>
-                                                            <a href="https://youtube.com/watch?v={{result.id}}"
-                                                               target="_blank">View Video In YouTube</a>
-                                                        </p>
-                                                        <a href="#" class="secondary-content" id="addSong"
-                                                           data-result="{{result.id}}"><i class="material-icons"
-                                                                                          data-result="{{result.id}}">add</i></a>
-                                                    </li>
-                                                {{/each}}
-                                            </ul>
-                                        </div>
-                                    </div>
-                                {{/if}}
-                            </div>
-                        {{/if}}
-                    {{else}}
-                        <div class="row" id="import-playlist">
-                            <div class="input-field">
-                                <input id="playlist-url" type="text" class="validate">
-                                <label for="search_for_song">Playlist URL</label>
-                            </div>
-                            <div class="progress">
-                                <div class="determinate" id="import-progress" style="width: 0%"></div>
-                            </div>
-                            <a class="waves-effect waves-light btn" id="import-playlist-button">Import
-                                Playlist</a>
-                            {{#if playlistImportVideosActive}}
-                                <a class="waves-effect waves-light btn" id="confirm-import">Confirm selection
-                                    and add songs to queue</a>
-                                <div class="input-field col l12 m12 s12">
-                                    <select multiple id="genres_pl">
-                                        <option value="" disabled selected>Select Genre(s):</option>
-                                        <option value="edm">EDM</option>
-                                        <option value="chill">Chill</option>
-                                        <option value="pop">Pop</option>
-                                        <option value="country">Country</option>
-                                        <option value="rock">Rock</option>
-                                        <option value="randb">R&B</option>
-                                        <option value="rap">Rap</option>
-                                        <option value="heavymetal">Heavy Metal</option>
-                                        <option value="christmas">Christmas</option>
-                                        <option value="alternative">Alternative</option>
-                                    </select>
-                                    <label class="white-text">Genre(s)</label>
-                                </div>
-                                <script>
-                                    $('#genres_pl').material_select();
-                                </script>
-                                <div id="import-playlist-results">
-                                    <ul class="collection teal-text">
-                                        {{#each result in importPlaylistVideos}}
-                                            <li class="collection-item avatar youtube-search-result-li">
-                                                <img src="{{result.image}}"
-                                                     onerror="this.src='http://static.boredpanda.com/blog/wp-content/uploads/2014/04/amazing-fox-photos-182.jpg'"
-                                                     alt="" class="video-import-thumbnail">
-                                                <span class="title video-import-text">{{result.title}}</span>
-                                                <p class="video-import-text">{{result.artist}} <br>
-                                                    <a href="https://youtube.com/watch?v={{result.id}}"
-                                                       target="_blank">View Video In YouTube</a>
-                                                </p>
-                                                <a href="#" class="secondary-content" id="removeSong"
-                                                   data-result="{{result.id}}"><i class="material-icons"
-                                                                                  data-result="{{result.id}}">remove</i></a>
-                                            </li>
-                                        {{/each}}
-                                    </ul>
-                                </div>
-                            {{/if}}
-                        </div>
-                    {{/if}}
-                </form>
-                <div id="song-results"></div>
-            </div>
-            <div class="row">
-                <form class="black-text hide" id="add-info">
-                    <div class="row">
-                        <div class="input-field">
-                            <input id="song-id" type="text" class="validate">
-                            <label for="song-id">Song ID</label>
-                        </div>
-                    </div>
-                    <div class="row">
-                        <a class="waves-effect waves-light btn" id="add-song-button"><i
-                                class="material-icons left">playlist_add</i>Request Song</a>
-                    </div>
-                </form>
-            </div>
-        </div>
-        <div class="divider"></div>
-        <div class="modal-footer">
-            <a href="#" class="modal-action modal-close waves-effect btn">Close</a>
-        </div>
-    </div>
-    <!--Report modal-->
-    <div id="report_modal" class="modal">
-        <div class="modal-content">
-            <h4>Reporting:</h4>
-            <h5 id="report-which">{{reportingSong.title}} <span class="thin">by</span> {{reportingSong.artist}}</h5>
-            <div class="report-layer-1">
-                <div>
-                    <input type="checkbox" id="report-song">
-                    <label for="report-song">
-                        Song
-                    </label>
-                </div>
-                <!-- Layer2 -->
-                {{#if reportSong}}
-                    <div class="report-layer-2" id="report-song-list">
-                        <p>
-                            <input type="checkbox" id="report-song-not-playing">
-                            <label for="report-song-not-playing">
-                                Not playing
-                            </label>
-                        </p>
-                        <p>
-                            <input type="checkbox" id="report-song-does-not-exist">
-                            <label for="report-song-does-not-exist">
-                                Does not exist
-                            </label>
-                        </p>
-                    <p>
-                        <input type="checkbox" id="report-song-other">
-                        <label for="report-song-other">
-                            Other:
-                        </label>
-                        {{#if reportSongOther}}
-                            <div class="input-field">
-                                <textarea class="materialize-textarea" id="report-song-other-ta" type="text"></textarea>
-                                <label for="report-song-other-ta">What is the issue?</label>
-                            </div>
-                        {{/if}}
-                        </p>
-                    </div>
-                {{/if}}
-                <div class="checkbox">
-                    <input type="checkbox" id="report-title">
-                    <label for="report-title">
-                        Title
-                    </label>
-                </div>
-                <!-- Layer2 -->
-                {{#if reportTitle}}
-                    <div class="report-layer-2" id="report-title-list">
-                        <p>
-                            <input type="checkbox" id="report-title-incorrect">
-                            <label for="report-title-incorrect">
-                                Incorrect
-                            </label>
-                        </p>
-                        <p>
-                            <input type="checkbox" id="report-title-inappropriate">
-                            <label for="report-title-inappropriate">
-                                Inappropriate
-                            </label>
-                        </p>
-                    <p>
-                        <input type="checkbox" id="report-title-other">
-                        <label for="report-title-other">
-                            Other:
-                        </label>
-                        {{#if reportTitleOther}}
-                            <div class="input-field">
-                                <textarea class="materialize-textarea" id="report-title-other-ta" type="text"></textarea>
-                                <label for="report-title-other-ta">What is the issue?</label>
-                            </div>
-                        {{/if}}
-                        </p>
-                    </div>
-                {{/if}}
-                <div>
-                    <input type="checkbox" id="report-artist">
-                    <label for="report-artist">
-                        Artist
-                    </label>
-                </div>
-                <!-- Layer2 -->
-                {{#if reportArtist}}
-                    <div class="report-layer-2" id="report-artist-list">
-                        <p>
-                            <input type="checkbox" id="report-artist-incorrect">
-                            <label for="report-artist-incorrect">
-                                Incorrect
-                            </label>
-                        </p>
-                        <p>
-                            <input type="checkbox" id="report-artist-inappropriate">
-                            <label for="report-artist-inappropriate">
-                                Inappropriate
-                            </label>
-                        </p>
-                    <p>
-                        <input type="checkbox" id="report-artist-other">
-                        <label for="report-artist-other">
-                            Other:
-                        </label>
-                        {{#if reportArtistOther}}
-                            <div class="input-field">
-                                <textarea class="materialize-textarea" id="report-artist-other-ta" type="text"></textarea>
-                                <label for="report-artist-other-ta">What is the issue?</label>
-                            </div>
-                        {{/if}}
-                        </p>
-                    </div>
-                {{/if}}
-                <div class="checkbox">
-                    <input type="checkbox" id="report-duration">
-                    <label for="report-duration">
-                        Duration
-                    </label>
-                </div>
-                <!-- Layer2 -->
-                {{#if reportDuration}}
-                    <div class="report-layer-2" id="report-duration-list">
-                        <div class="checkbox">
-                            <input type="checkbox" id="report-duration-long">
-                            <label for="report-duration-long">
-                                Too long
-                            </label>
-                        </div>
-                        <div class="checkbox">
-                            <input type="checkbox" id="report-duration-short">
-                            <label for="report-duration-short">
-                                Too short
-                            </label>
-                        </div>
-                        <div class="checkbox">
-                            <input type="checkbox" id="report-duration-other">
-                            <label for="report-duration-other">
-                                Other: <br>
-                            </label>
-                            {{#if reportDurationOther}}
-                                <div class="input-field">
-                                    <textarea class="materialize-textarea" id="report-duration-other-ta" type="text"></textarea>
-                                    <label for="report-duration-other-ta">What is the issue?</label>
-                                </div>
-                            {{/if}}
-                        </div>
-                    </div>
-                {{/if}}
-                <div class="checkbox">
-                    <input type="checkbox" id="report-albumart">
-                    <label for="report-albumart">
-                        Albumart
-                    </label>
-                </div>
-                <!-- Layer2 -->
-                {{#if reportAlbumart}}
-                    <div class="report-layer-2" id="report-albumart-list">
-                        <div class="checkbox">
-                            <input type="checkbox" id="report-albumart-incorrect">
-                            <label for="report-albumart-incorrect">
-                                Incorrect
-                            </label>
-                        </div>
-                        <div class="checkbox">
-                            <input type="checkbox" id="report-albumart-inappropriate">
-                            <label for="report-albumart-inappropriate">
-                                Inappropriate
-                            </label>
-                        </div>
-                        <div class="checkbox">
-                            <input type="checkbox" id="report-albumart-not-showing">
-                            <label for="report-albumart-not-showing">
-                                Not showing
-                            </label>
-                        </div>
-                        <div class="checkbox">
-                            <input type="checkbox" id="report-albumart-other">
-                            <label for="report-albumart-other">
-                                Other:
-                            </label>
-                            {{#if reportAlbumartOther}}
-                                <div class="input-field">
-                                    <textarea class="materialize-textarea" id="report-albumart-other-ta" type="text"></textarea>
-                                    <label for="report-albumart-other-ta">What is the issue?</label>
-                                </div>
-                            {{/if}}
-                        </div>
-                    </div>
-                {{/if}}
-                <div class="checkbox">
-                    <input type="checkbox" id="report-other">
-                    <label for="report-other">
-                        Other: <br>
-                    </label>
-                    {{#if reportOther}}
-                        <div class="input-field">
-                            <textarea class="materialize-textarea" id="report-other-ta" type="text"></textarea>
-                            <label for="report-other-ta">What is the issue?</label>
-                        </div>
-                    {{/if}}
-                </div>
-            </div>
-            <a id="report-song-button" class="waves-effect waves-light btn btn-block red">Submit Song Report</a>
-            {{#if previousSongR}}
-                <a id="report-prev" class="waves-effect waves-light btn btn-block">Report Previous Song</a>
-            {{/if}}
-            <a id="report-curr" class="waves-effect waves-light btn btn-block">Report Current Song</a>
-        </div>
-        <div class="divider"></div>
-        <div class="modal-footer">
-            <a class="modal-action modal-close waves-effect btn">Close</a>
-        </div>
-    </div>
     <script>
-        $("#add-song-modal-button").leanModal({
-            dismissible: true,
-            opacity: .5,
-            in_duration: 500,
-            out_duration: 200
-        });
-        $("#report-modal-button").leanModal({
-            dismissible: true,
-            opacity: .5,
-            in_duration: 500,
-            out_duration: 200,
-            ready: function() {
-                Session.set("currentSongR", Session.get("currentSong"));
-                Session.set("previousSongR", Session.get("previousSong"));
-            }
-        });
         $(".dropdown-button").dropdown({
             belowOrigin: true
         });

+ 2 - 2
app/lib/collections.js

@@ -1,7 +1,7 @@
 Playlists = new Mongo.Collection("playlists");
-//UserPlaylists = new Mongo.Collection("user_playlists");
+PrivatePlaylists = new Mongo.Collection("private_playlists");
 Rooms = new Mongo.Collection("rooms");
-//PrivateRooms = new Mongo.Collection("private_rooms");
+PrivateRooms = new Mongo.Collection("private_rooms");
 Queues = new Mongo.Collection("queues");
 Reports = new Mongo.Collection("reports");
 Chat = new Mongo.Collection("chat");

+ 133 - 1
app/lib/schemas.js

@@ -61,6 +61,23 @@ Schemas.FullSong = new SimpleSchema({
     }
 });
 
+Schemas.UserSong = new SimpleSchema({
+    "id": {
+        type: String,
+        label: "Song YouTube id"
+    },
+    "title": {
+        type: String,
+        label: "Song title"
+    },
+    "duration": {
+        type: Number,
+        label: "Song duration",
+        min: 0,
+        decimal: true
+    }
+});
+
 Schemas.QueueSong = new SimpleSchema({
     "id": {
         type: String,
@@ -253,6 +270,119 @@ Schemas.Room = new SimpleSchema({
     }
 });
 
+Schemas.PrivateRoom = new SimpleSchema({
+    name: {
+        type: String,
+        label: "Room Name",
+        regEx: /^[a-z0-9A-Z_\s]{1,30}$/
+    },
+    displayName: {
+        type: String,
+        label: "Room Display Name"
+    },
+    currentSong: {
+        type: Object,
+        defaultValue: {song: {id: "60ItHLz5WEA", duration: 213, title: "Alan Walker - Faded"}, started: 0},
+        label: "Current Song Object"
+    },
+    "currentSong.song": {
+        type: Schemas.UserSong,
+        label: "Current Song Object"
+    },
+    "currentSong.started": {
+        type: Number,
+        label: "Current Song Start Date"
+    },
+    timePaused: {
+        type: Number,
+        defaultValue: 0,
+        label: "Amount of time a room has been paused for"
+    },
+    users: {
+        type: Number,
+        defaultValue: 0,
+        label: "Users Online",
+        min: 0
+    },
+    state: {
+        type: String,
+        defaultValue: "paused",
+        allowedValues: ["paused", "playing"],
+        label: "Room State"
+    },
+    votes: {
+        type: Number,
+        defaultValue: 0,
+        label: "Current votes to skip current song",
+        min: 0
+    },
+    private: {
+        type: Boolean,
+        defaultValue: false,
+        label: "Room private or not"
+    },
+    roomDesc: {
+        type: String,
+        label: "Room description"
+    },
+    userList: {
+        type: Array,
+        label: "List of currently online people",
+        defaultValue: []
+    },
+    "userList.$": {
+        type: String,
+        label: "Username of user currently in a room"
+    },
+    allowed: {
+        type: Array,
+        label: "List of allowed users in the room",
+        defaultValue: []
+    },
+    "allowed.$": {
+        type: String,
+        label: "Username of user allowed in room"
+    },
+    "playlist": {
+        type: String,
+        optional: true,
+        label: "Playlist in room"
+    },
+    "owner": {
+        type: String,
+        label: "Username of owner"
+    },
+    lastSong: {
+        type: Number,
+        label: "Index of the previous song",
+        defaultValue: 0
+    }
+});
+
+Schemas.PrivatePlaylist = new SimpleSchema({
+    name: {
+        type: String,
+        label: "Name of playlist",
+        regEx: /^[a-z0-9_]{1,20}$/
+    },
+    displayName: {
+        type: String,
+        label: "Displayname of playlist"
+    },
+    songs: {
+        type: Array,
+        label: "Array of song objects"
+    },
+    "songs.$": {
+        type: Schemas.UserSong,
+        label: "Song Object"
+    },
+    owner: {
+        type: String,
+        label: "Owner of playlist"
+    }
+});
+
 Schemas.Playlist = new SimpleSchema({
     type: {
         type: String,
@@ -543,4 +673,6 @@ Meteor.users.attachSchema(Schemas.User);
 Reports.attachSchema(Schemas.Report);
 Feedback.attachSchema(Schemas.Feedback);
 Songs.attachSchema(Schemas.FullSong);
-News.attachSchema(Schemas.Article);
+News.attachSchema(Schemas.Article);
+PrivateRooms.attachSchema(Schemas.PrivateRoom);
+PrivatePlaylists.attachSchema(Schemas.PrivatePlaylist);

+ 293 - 1
app/server/server.js

@@ -45,9 +45,17 @@ var default_song = {
     genres: ["edm", "pop"]
 };
 
+var default_private_playlist = {
+    name: "default",
+    displayName: "Default Playlist",
+    songs: [{id: "60ItHLz5WEA", duration: 213, title: "Alan Walker - Faded"}],
+    owner: "NONE"
+};
+
 Alerts.update({active: true}, {$set: {active: false}}, {multi: true});
 
 var stations = [];
+var privateStations = [];
 var voteNum = 0;
 
 var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_";
@@ -120,6 +128,15 @@ function getStation(type, cb) {
     });
 }
 
+function getPrivateStation(name, cb) {
+    privateStations.forEach(function (station) {
+        if (station.name === name) {
+            cb(station);
+            return;
+        }
+    });
+}
+
 function createRoom(display, tag, private, desc) {
     var type = tag;
     if (Rooms.find({type: type}).count() === 0) {
@@ -142,6 +159,26 @@ function createRoom(display, tag, private, desc) {
     }
 }
 
+function createPrivateRoom(name, display, private, desc, owner) {
+    if (PrivateRooms.find({name: name}).count() === 0) {
+        PrivateRooms.insert({
+            name: name,
+            displayName: display,
+            private: private,
+            roomDesc: desc,
+            owner: owner
+        }, function (err) {
+            if (err) {
+                throw err;
+            } else {
+                privateStations.push(new PrivateStation(name));
+            }
+        });
+    } else {
+        return "Private room with that name already exists";
+    }
+}
+
 function Station(type) {
     if (Playlists.find({type: type}).count() === 0) {
         Playlists.insert({type: type, songs: [default_song.mid], lastSong: 0});
@@ -173,7 +210,7 @@ function Station(type) {
                 usersObj[username]--;
                 var list = Rooms.findOne({type: type}).userList;
                 var index = list.indexOf(username);
-                if (index >= 0) { //TODO Fix this
+                if (index >= 0) {
                     list.splice(index, 1);
                     Rooms.update({type: type}, {$set: {userList: list}});
                 }
@@ -344,6 +381,166 @@ function Station(type) {
     this.voted = [];
 }
 
+function PrivateStation(name) {
+    var usersObj = {};
+    PrivateRooms.update({name: name}, {$set: {userList: []}});
+    Meteor.publish("pr_" + name, function () {
+        var user = Meteor.users.findOne(this.userId);
+        if (this.userId !== undefined && user !== undefined && user.profile !== undefined && user.profile.username !== undefined) {
+            var username = user.profile.username;
+            if (usersObj[username] === undefined) {
+                usersObj[username] = 1;
+            } else {
+                usersObj[username]++;
+            }
+            PrivateRooms.update({name: name}, {$push: {userList: username}});
+            this.onStop(function() {
+                usersObj[username]--;
+                var list = PrivateRooms.findOne({name: name}).userList;
+                var index = list.indexOf(username);
+                if (index >= 0) {
+                    list.splice(index, 1);
+                    PrivateRooms.update({name: name}, {$set: {userList: list}});
+                }
+            });
+        }
+        return undefined;
+    });
+    var self = this;
+    var startedAt = Date.now();
+    var _room = PrivateRooms.findOne({name: name});
+    var playlist = PrivatePlaylists.findOne({name: _room.playlist, owner: _room.owner});
+    if (playlist === undefined) {
+        playlist = default_private_playlist;
+    }
+    var songs = playlist.songs;
+
+    var currentSong = 0;
+    if (currentSong < (songs.length - 1)) {
+        currentSong++;
+    } else currentSong = 0;
+    var currentId = songs[currentSong];
+
+    var song = songs[songs.indexOf(currentId)];
+    if (song === undefined) {
+        song = {song: {id: "60ItHLz5WEA", duration: 213, title: "Alan Walker - Faded"}, started: 0};
+    }
+    var res = PrivateRooms.update({name: name}, {
+        $set: {
+            currentSong: {song: song, started: startedAt},
+            users: 0
+        }
+    });
+
+    this.skipSong = function () {
+        self.voted = [];
+        voteNum = 0;
+        PrivateRooms.update({name: name}, {$set: {votes: 0}});
+        playlist = PrivatePlaylists.findOne({name: _room.playlist, owner: _room.owner});
+        if (playlist === undefined) {
+            playlist = default_private_playlist;
+        }
+        songs = playlist.songs;
+        songs.forEach(function (id, index) {
+            if (id === currentId) {
+                currentSong = index;
+            }
+        });
+        if (currentSong < (songs.length - 1)) {
+            currentSong++;
+        } else currentSong = 0;
+        currentId = songs[currentSong];
+        PrivateRooms.update({name: name}, {$set: {timePaused: 0}});
+        this.songTimer();
+        PrivateRooms.update({name: name}, {$set: {currentSong: {song: songs[currentSong], started: startedAt}}});
+    };
+
+    PrivateRooms.update({name: name}, {$set: {timePaused: 0}});
+
+    var timer;
+    var timerInitialised = false;
+
+    this.songTimer = function () {
+        if (state !== "paused") {
+            startedAt = Date.now();
+
+            if (timer !== undefined) {
+                timer.pause();
+            }
+            timerInitialised = true;
+            timer = new Timer(function () {
+                self.skipSong();
+            }, songs[currentSong].duration * 1000);
+        }
+    };
+
+    var state = PrivateRooms.findOne({name: name}).state;
+
+    this.pauseRoom = function () {
+        if (state !== "paused") {
+            timer.pause();
+            PrivateRooms.update({name: name}, {$set: {state: "paused"}});
+            state = "paused";
+        }
+    };
+    this.resumeRoom = function () {
+        if (state !== "playing") {
+            if (!timerInitialised) {
+                timer = new Timer(function () {
+                    self.skipSong();
+                }, songs[currentSong] * 1000);
+            }
+            timer.resume();
+            PrivateRooms.update({name: name}, {$set: {state: "playing", timePaused: timer.timeWhenPaused()}});
+            state = "playing";
+        }
+    };
+    this.cancelTimer = function () {
+        timer.pause();
+    };
+    this.getState = function () {
+        return state;
+    };
+    this.name = name;
+
+    var private = PrivateRooms.findOne({name: name}).private;
+
+    if (typeof private !== "boolean") {
+        PrivateRooms.update({name: name}, {$set: {"private": false}});
+        private = false;
+    }
+
+    this.private = private;
+
+    this.unlock = function () {
+        if (self.private) {
+            self.private = false;
+            PrivateRooms.update({name: name}, {$set: {"private": false}});
+        }
+    };
+
+    this.lock = function () {
+        if (!self.private) {
+            self.private = true;
+            PrivateRooms.update({name: name}, {$set: {"private": true}});
+        }
+    };
+
+    this.setPlaylist = function (plName) {
+        if (PrivatePlaylists.findOne({name: plName, owner: _room.owner}) !== undefined) {
+            PrivateRooms.update({name: name}, {$set: {"playlist": plName}});
+            _room.playlist = plName;
+            playlist = PrivatePlaylists({name: plName, owner: _room.owner});
+            songs = playlist.songs;
+
+            currentSong = 0;
+        }
+    };
+
+    this.skipSong();
+    this.voted = [];
+}
+
 function shuffle(array) {
     var currentIndex = array.length, temporaryValue, randomIndex;
 
@@ -453,6 +650,11 @@ Rooms.find({}).fetch().forEach(function (room) {
     }
 });
 
+PrivateRooms.find({}).fetch().forEach(function (room) {
+    var name = room.name;
+    privateStations.push(new PrivateStation(name));
+});
+
 Accounts.validateNewUser(function (user) {
     var username;
     if (user.services) {
@@ -518,10 +720,19 @@ Meteor.publish("playlists", function () {
     return Playlists.find({})
 });
 
+Meteor.publish("private_playlists", function () {
+    return PrivatePlaylists.find({})
+});
+
+
 Meteor.publish("rooms", function () {
     return Rooms.find({});
 });
 
+Meteor.publish("private_rooms", function () {
+    return PrivateRooms.find({});
+});
+
 Meteor.publish("songs", function () {
     return Songs.find({});
 });
@@ -581,6 +792,16 @@ Meteor.publish("feedback", function(){
     return Feedback.find();
 })
 
+function isPrivateRoomOwner(name) {
+    var userData = Meteor.users.find(Meteor.userId());
+    var room = PrivateRooms.findOne({name: name});
+    if (Meteor.userId() && userData.count !== 0 && room !== undefined && userData.fetch()[0].profile.username === room.owner) {
+        return true;
+    } else {
+        return false;
+    }
+}
+
 function isAdmin() {
     var userData = Meteor.users.find(Meteor.userId());
     if (Meteor.userId() && userData.count !== 0 && userData.fetch()[0].profile.rank === "admin") {
@@ -687,6 +908,24 @@ Meteor.methods({
             throw new Meteor.Error(403, "Invalid permissions.");
         }
     },
+    lockPrivateRoom: function (name) {
+        if ((isAdmin() || isPrivateRoomOwner()) && !isBanned()) {
+            getPrivateStation(name, function (station) {
+                station.lock();
+            });
+        } else {
+            throw new Meteor.Error(403, "Invalid permissions.");
+        }
+    },
+    unlockPrivateRoom: function (name) {
+        if ((isAdmin() || isPrivateRoomOwner) && !isBanned()) {
+            getPrivateStation(name, function (station) {
+                station.unlock();
+            });
+        } else {
+            throw new Meteor.Error(403, "Invalid permissions.");
+        }
+    },
     banUser: function (username, period, reason) {
         if (isAdmin() && !isBanned()) {
             var user = Meteor.user();
@@ -974,6 +1213,43 @@ Meteor.methods({
             throw new Meteor.Error(403, "Invalid permissions.");
         }
     },
+    skipPrivateSong: function (name) {
+        if ((isAdmin() || isRoomOwner()) && !isBanned()) {
+            getPrivateStation(name, function (station) {
+                if (station === undefined) {
+                    throw new Meteor.Error(404, "Station not found.");
+                } else {
+                    station.skipSong();
+                }
+            });
+        }
+    },
+    pausePrivateRoom: function (name) {
+        if ((isAdmin() || isPrivateRoomOwner()) && !isBanned()) {
+            getPrivateStation(name, function (station) {
+                if (station === undefined) {
+                    throw new Meteor.Error(403, "Room doesn't exist.");
+                } else {
+                    station.pauseRoom();
+                }
+            });
+        } else {
+            throw new Meteor.Error(403, "Invalid permissions.");
+        }
+    },
+    resumePrivateRoom: function (name) {
+        if ((isAdmin() || isPrivateRoomOwner()) && !isBanned()) {
+            getPrivateStation(name, function (station) {
+                if (station === undefined) {
+                    throw new Meteor.Error(403, "Room doesn't exist.");
+                } else {
+                    station.resumeRoom();
+                }
+            });
+        } else {
+            throw new Meteor.Error(403, "Invalid permissions.");
+        }
+    },
     createUserMethod: function (formData, captchaData) {
         if (!isBanned()) {
             var verifyCaptchaResponse = reCAPTCHA.verifyCaptcha(this.connection.clientAddress, captchaData);
@@ -1171,6 +1447,22 @@ Meteor.methods({
             throw new Meteor.Error(403, "Invalid permissions.");
         }
     },
+    createPrivateRoom: function (name, display, private, desc) {
+        if (Meteor.userId() && !isBanned()) {
+            createPrivateRoom(name, display, private, desc, Meteor.user().profile.username);
+        } else {
+            throw new Meteor.Error(403, "Invalid permissions.");
+        }
+    },
+    createPrivatePlaylist: function (name, display) {
+        if (Meteor.userId() && !isBanned()) {
+            if (PrivatePlaylists.findOne({name: name, owner: Meteor.user().profile.username}) === undefined) {
+                PrivatePlaylists.insert({name: name, displayName: display, songs: [{id: "60ItHLz5WEA", duration: 213, title: "Alan Walker - Faded"}], owner: Meteor.user().profile.username});
+            }
+        } else {
+            throw new Meteor.Error(403, "Invalid permissions.");
+        }
+    },
     deleteRoom: function (type) {
         if (isAdmin() && !isBanned()) {
             Rooms.remove({type: type});