Working datachannel, youtube support & server fix
This commit is contained in:
49
client/package-lock.json
generated
49
client/package-lock.json
generated
@@ -5727,6 +5727,11 @@
|
||||
"integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
|
||||
"dev": true
|
||||
},
|
||||
"get-youtube-id": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-youtube-id/-/get-youtube-id-1.0.1.tgz",
|
||||
"integrity": "sha512-5yidLzoLXbtw82a/Wb7LrajkGn29BM6JuLWeHyNfzOGp1weGyW4+7eMz6cP23+etqj27VlOFtq8fFFDMLq/FXQ=="
|
||||
},
|
||||
"getpass": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
|
||||
@@ -7058,6 +7063,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"load-script": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/load-script/-/load-script-1.0.0.tgz",
|
||||
"integrity": "sha1-BJGTngvuVkPuSUp+PaPSuscMbKQ="
|
||||
},
|
||||
"loader-fs-cache": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/loader-fs-cache/-/loader-fs-cache-1.0.3.tgz",
|
||||
@@ -9923,6 +9933,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sister": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/sister/-/sister-3.0.2.tgz",
|
||||
"integrity": "sha512-p19rtTs+NksBRKW9qn0UhZ8/TUI9BPw9lmtHny+Y3TinWlOa9jWh9xB0AtPSdmOy49NJJJSSe0Ey4C7h0TrcYA=="
|
||||
},
|
||||
"slash": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
|
||||
@@ -11314,6 +11329,15 @@
|
||||
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
|
||||
"dev": true
|
||||
},
|
||||
"vue-youtube": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/vue-youtube/-/vue-youtube-1.4.0.tgz",
|
||||
"integrity": "sha512-PCyfGAouSt6rTX0GLUzpdX2XC52zYf7a9mUhdp53jeDlPoU40hpsvyV3Zg2+947pvbv27ORcmtzm2fqO08kh9Q==",
|
||||
"requires": {
|
||||
"get-youtube-id": "^1.0.0",
|
||||
"youtube-player": "^5.4.0"
|
||||
}
|
||||
},
|
||||
"vuex": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.5.1.tgz",
|
||||
@@ -12328,6 +12352,31 @@
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"youtube-player": {
|
||||
"version": "5.5.2",
|
||||
"resolved": "https://registry.npmjs.org/youtube-player/-/youtube-player-5.5.2.tgz",
|
||||
"integrity": "sha512-ZGtsemSpXnDky2AUYWgxjaopgB+shFHgXVpiJFeNB5nWEugpW1KWYDaHKuLqh2b67r24GtP6HoSW5swvf0fFIQ==",
|
||||
"requires": {
|
||||
"debug": "^2.6.6",
|
||||
"load-script": "^1.0.0",
|
||||
"sister": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"register-service-worker": "^1.7.1",
|
||||
"vue": "^2.6.11",
|
||||
"vue-router": "^3.2.0",
|
||||
"vue-youtube": "^1.4.0",
|
||||
"vuex": "^3.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
13
client/src/components/Player.vue
Normal file
13
client/src/components/Player.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<div id="player"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -4,11 +4,13 @@ import './registerServiceWorker'
|
||||
import router from './router'
|
||||
import store from './store'
|
||||
import Buefy from 'buefy'
|
||||
import VueYoutube from 'vue-youtube'
|
||||
|
||||
import 'buefy/dist/buefy.css'
|
||||
|
||||
// import './assets/style.scss'
|
||||
|
||||
Vue.use(Buefy)
|
||||
Vue.use(VueYoutube)
|
||||
Vue.config.productionTip = false
|
||||
|
||||
new Vue({
|
||||
|
||||
@@ -3,7 +3,16 @@ const state = {
|
||||
roomStatus: {
|
||||
roomName: '',
|
||||
roomCode: '',
|
||||
current: '',
|
||||
player: {
|
||||
timeCode: 0,
|
||||
playing: true
|
||||
},
|
||||
current: {
|
||||
link: '',
|
||||
title: '',
|
||||
votes: 0,
|
||||
voters: []
|
||||
},
|
||||
playlist: []
|
||||
}
|
||||
}
|
||||
@@ -23,6 +32,35 @@ const actions = {
|
||||
},
|
||||
setAdmin ({ commit }) {
|
||||
commit('SET_ADMIN')
|
||||
},
|
||||
vote ({ commit, dispatch, state }, { link, isPositive, voterName }) {
|
||||
console.log('vote on ' + link + ' (' + isPositive + ') by ' + voterName)
|
||||
if (isPositive) {
|
||||
commit('ADD_VOTE', {
|
||||
link: link,
|
||||
voterName: voterName
|
||||
})
|
||||
} else {
|
||||
commit('REMOVE_VOTE', {
|
||||
link: link,
|
||||
voterName: voterName
|
||||
})
|
||||
}
|
||||
dispatch('rtc/broadcast', { message: state.roomStatus, type: 'status' }, { root: true })
|
||||
},
|
||||
setCurrent ({ commit, dispatch }, { playerStatus, timeCode }) {
|
||||
switch (playerStatus) {
|
||||
case 0:
|
||||
commit('CURRENT_END')
|
||||
break
|
||||
case 1:
|
||||
commit('CURRENT_PLAY', timeCode)
|
||||
break
|
||||
case 2:
|
||||
commit('CURRENT_PAUSE', timeCode)
|
||||
break
|
||||
}
|
||||
dispatch('rtc/broadcast', { message: state.roomStatus, type: 'status' }, { root: true })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,8 +77,58 @@ const mutations = {
|
||||
SET_ADMIN (state) {
|
||||
state.admin = true
|
||||
},
|
||||
BROADCAST_ROOMSTATUS (state) {
|
||||
ADD_VOTE (state, { link, voterName }) {
|
||||
var play = state.roomStatus.playlist.find(play => play.link === link)
|
||||
if (play === undefined) {
|
||||
play = {
|
||||
link: link,
|
||||
votes: 1,
|
||||
voters: [
|
||||
voterName
|
||||
]
|
||||
}
|
||||
if (state.roomStatus.current.votes === 0) state.roomStatus.current = play
|
||||
else state.roomStatus.playlist.push(play)
|
||||
} else {
|
||||
play.votes++
|
||||
play.voters.push(voterName)
|
||||
}
|
||||
},
|
||||
REMOVE_VOTE (state, { link, voterName }) {
|
||||
var play = state.roomStatus.playlist.find(play => play.link === link)
|
||||
play.votes--
|
||||
const index = play.voters.indexOf(voterName)
|
||||
if (index > -1) {
|
||||
play.voters.splice(index, 1)
|
||||
}
|
||||
|
||||
if (play.vote === 0) {
|
||||
const index = state.roomStatus.playlist.indexOf(play)
|
||||
if (index > -1) {
|
||||
state.roomStatus.playlist.splice(index, 1)
|
||||
}
|
||||
}
|
||||
},
|
||||
CURRENT_END (state) {
|
||||
if (state.roomStatus.playlist.length === 0) {
|
||||
state.roomStatus.current.link = ''
|
||||
state.roomStatus.current.title = ''
|
||||
state.roomStatus.current.votes = 0
|
||||
state.roomStatus.current.voters = []
|
||||
} else {
|
||||
state.roomStatus.playlist.sort((a, b) => {
|
||||
return b.votes - a.votes
|
||||
})
|
||||
state.roomStatus.current = state.roomStatus.playlist.shift()
|
||||
}
|
||||
},
|
||||
CURRENT_PAUSE (state, timeCode) {
|
||||
state.roomStatus.player.playing = false
|
||||
state.roomStatus.player.timeCode = timeCode
|
||||
},
|
||||
CURRENT_PLAY (state, timeCode) {
|
||||
state.roomStatus.player.playing = true
|
||||
state.roomStatus.player.timeCode = timeCode
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,8 +39,8 @@ const actions = {
|
||||
leave ({ commit }) {
|
||||
commit('LEAVE')
|
||||
},
|
||||
broadcast ({ commit }, message) {
|
||||
commit('BROADCAST', message)
|
||||
broadcast ({ commit }, { message, type }) {
|
||||
commit('BROADCAST', { message: message, type: type })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ const mutations = {
|
||||
var peer = state.peers.find(peer => peer.name === target)
|
||||
|
||||
peer.dataChannel = peer.connection.createDataChannel('dataChannel')
|
||||
peer.dataChannel.onmessage = handleDataChannelMessage
|
||||
peer.dataChannel.onopen = handleDataChannelStateChangeEvent
|
||||
peer.dataChannel.onclose = handleDataChannelStateChangeEvent
|
||||
|
||||
@@ -127,9 +128,14 @@ const mutations = {
|
||||
})
|
||||
state.peers = {}
|
||||
},
|
||||
BROADCAST (state, message) {
|
||||
BROADCAST (state, { message, type }) {
|
||||
const data = JSON.stringify({
|
||||
type: type,
|
||||
message: message
|
||||
})
|
||||
console.log('[RTC] broadcast message ' + data)
|
||||
state.peers.forEach(peer => {
|
||||
peer.dataChannel.send(message)
|
||||
peer.dataChannel.send(data)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -167,17 +173,19 @@ function handleDataChannelCallback (event) {
|
||||
peer.dataChannel.onopen = handleDataChannelStateChangeEvent
|
||||
peer.dataChannel.onclose = handleDataChannelStateChangeEvent
|
||||
|
||||
store.dispatch('rtc/broadcast', store.state.room.roomStatus)
|
||||
store.dispatch('rtc/broadcast', { message: store.state.room.roomStatus, type: 'status' })
|
||||
}
|
||||
|
||||
function handleDataChannelMessage (event) {
|
||||
console.log('[RTC] data channel message ' + event.data)
|
||||
var data = event.data
|
||||
var data = JSON.parse(event.data)
|
||||
console.log('[RTC] data channel message type ' + data.type)
|
||||
switch (data.type) {
|
||||
case 'status':
|
||||
store.dispatch('room/setRoomStatus', data.message)
|
||||
break
|
||||
case 'vote':
|
||||
store.dispatch('room/vote', { link: data.message.link, isPositive: data.message.isPositive, voterName: data.message.voterName })
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,19 @@
|
||||
<h2 class="subtitle">{{roomStatus.current.title}}</h2>
|
||||
|
||||
<b-tabs>
|
||||
<b-tab-item label="Player" :visible="settings.showPlayer">
|
||||
<div class="playerDiv">
|
||||
<youtube
|
||||
:video-id="roomStatus.current.link"
|
||||
nocookie="true"
|
||||
ref="youtube"
|
||||
@playing="roomStatus.player.playing"
|
||||
></youtube>
|
||||
</div>
|
||||
</b-tab-item>
|
||||
|
||||
<b-tab-item label="Playlist">
|
||||
<b-button icon-left="plus" @click="addLinkPrompt">Add link</b-button>
|
||||
<b-table :data="roomStatus.playlist" striped hoverable default-sort="vote">
|
||||
<template slot-scope="props">
|
||||
<b-table-column field="title" label="Title">
|
||||
@@ -16,24 +28,37 @@
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column field="vote" label="Votes">
|
||||
{{props.row.vote}}
|
||||
{{props.row.votes}}
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column field="voters" label="Voters" :visible="isAdmin">
|
||||
{{props.row.voters}}
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column>
|
||||
<b-button icon-left="arrow-up-bold-outline" type="is-dark"/>
|
||||
<b-button icon-left="arrow-down-bold-outline" type="is-dark"/>
|
||||
<b-button v-if="hasVoted(props.row)" icon-left="arrow-down-bold-outline" type="is-dark" @click="vote (props.row.link, false)"/>
|
||||
<b-button v-else icon-left="arrow-up-bold-outline" type="is-dark" @click="vote (props.row.link, true)"/>
|
||||
</b-table-column>
|
||||
</template>
|
||||
</b-table>
|
||||
</b-tab-item>
|
||||
|
||||
<b-tab-item label="Admin" :visible="showAdmin">
|
||||
<b-tab-item label="Admin" :visible="isAdmin">
|
||||
<b-button @click="broadcastStatus">Force status update</b-button>
|
||||
<b-table :data="usersList" striped hoverable>
|
||||
<template slot-scope="props">
|
||||
<b-table-column field="name" label="Name">
|
||||
{{props.row.name}}
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column field="connection" label="Connection">
|
||||
{{props.row.connection.signalingState}}
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column field="data" label="DataChannel">
|
||||
{{props.row.dataChannel.readyState}}
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column>
|
||||
<b-button icon-left="karate" type="is-dark"/>
|
||||
</b-table-column>
|
||||
@@ -45,7 +70,17 @@
|
||||
<h1 class="subtitle">{{roomStatus.roomCode}}</h1>
|
||||
</b-tab-item>
|
||||
|
||||
<b-tab-item label="Settings">
|
||||
<div class="field">
|
||||
<b-switch v-model="settings.playLink">Play link</b-switch>
|
||||
</div>
|
||||
<div class="field">
|
||||
<b-switch v-model="settings.showPlayer" :disabled="!settings.playLink">Show player</b-switch>
|
||||
</div>
|
||||
</b-tab-item>
|
||||
|
||||
</b-tabs>
|
||||
<b-loading is-full-page :active.sync="isRoomLoading"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -53,10 +88,13 @@
|
||||
export default {
|
||||
name: 'Room',
|
||||
computed: {
|
||||
player () {
|
||||
return this.$refs.youtube.player
|
||||
},
|
||||
roomStatus () {
|
||||
return this.$store.state.room.roomStatus
|
||||
},
|
||||
showAdmin () {
|
||||
isAdmin () {
|
||||
return this.$store.state.room.admin
|
||||
},
|
||||
usersList () {
|
||||
@@ -65,14 +103,86 @@ export default {
|
||||
},
|
||||
isLoggedIn () {
|
||||
return this.$store.state.app.loginSuccess
|
||||
},
|
||||
isRoomLoading () {
|
||||
return this.roomStatus.roomName === ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
settings: {
|
||||
playLink: true,
|
||||
showPlayer: true
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
if (!this.isLoggedIn) this.$router.push({ name: 'Home' })
|
||||
this.player.addEventListener('onStateChange', this.playerStateChange)
|
||||
this.player.playVideo()
|
||||
},
|
||||
watch: {
|
||||
roomStatus: function (status) {
|
||||
this.player.seekTo(status.player.timeCode, true)
|
||||
if (status.player.playing) this.player.playVideo()
|
||||
else this.player.pauseVideo()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
broadcastStatus () {
|
||||
this.$store.dispatch('rtc/broadcast', { message: this.$store.state.room.roomStatus, type: 'status' })
|
||||
},
|
||||
addLinkPrompt () {
|
||||
this.$buefy.dialog.prompt({
|
||||
message: 'Add a youtube link',
|
||||
trapFocus: true,
|
||||
inputAttrs: {
|
||||
placeholder: 'https://www.youtube.com/watch?v=YItIK09bpKk',
|
||||
minlength: 10
|
||||
},
|
||||
cancelText: 'Nah',
|
||||
confirmText: 'Add',
|
||||
onConfirm: (link) => this.addLink(link)
|
||||
})
|
||||
},
|
||||
addLink (link) {
|
||||
const linkID = this.$youtube.getIdFromUrl(link)
|
||||
if (linkID === null) {
|
||||
this.$buefy.toast.open('Invalid youtube link')
|
||||
return
|
||||
}
|
||||
if (this.isAdmin) {
|
||||
this.$store.dispatch('room/vote', { link: linkID, isPositive: true, voterName: this.$store.state.rtc.name })
|
||||
} else {
|
||||
this.vote(linkID, true)
|
||||
}
|
||||
},
|
||||
vote (link, isPositive) {
|
||||
const message = {
|
||||
type: 'vote',
|
||||
link: link,
|
||||
isPositive: isPositive,
|
||||
voterName: this.$store.state.rtc.name
|
||||
}
|
||||
this.$store.dispatch('rtc/broadcast', { message: message, type: 'vote' })
|
||||
},
|
||||
hasVoted (play) {
|
||||
return play.voters.includes(this.$store.state.rtc.name)
|
||||
},
|
||||
async playerStateChange (event) {
|
||||
console.log('[PLAYER] Status change ' + event.data)
|
||||
if (this.isAdmin) this.$store.dispatch('room/setCurrent', { playerStatus: event.data, timeCode: await this.$refs.youtube.player.getCurrentTime() })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.room {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.playerDiv {
|
||||
height: 500px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -98,7 +98,7 @@ public class RoomManager implements IRoomManager{
|
||||
return;
|
||||
}
|
||||
|
||||
System.err.println("[ROOM] Foward RTC");
|
||||
System.err.println("[ROOM] Foward RTC message");
|
||||
followMessage(targetSession, message);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import org.json.JSONObject;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.handler.TextWebSocketHandler;
|
||||
@@ -22,7 +23,7 @@ public class SocketHandler extends TextWebSocketHandler {
|
||||
|
||||
@Override
|
||||
public void handleTextMessage(WebSocketSession session, TextMessage message) throws InterruptedException, IOException {
|
||||
System.err.println("SOCKET MESSAGE :" + message.getPayload());
|
||||
System.err.println("[WS] message :" + message.getPayload());
|
||||
|
||||
String payload = message.getPayload();
|
||||
JSONObject jsonObject = new JSONObject(payload);
|
||||
@@ -59,4 +60,11 @@ public class SocketHandler extends TextWebSocketHandler {
|
||||
System.err.println("[WS] new connection");
|
||||
sessions.add(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus){
|
||||
System.err.println("[WS] connection closed");
|
||||
sessions.remove(session);
|
||||
roomManager.leave(session);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user