Compare commits
10 Commits
6e2d52cba9
...
v2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
663a8ca8b6 | ||
|
|
63eca90f0e | ||
|
|
c1b3280fff | ||
|
|
167dcc354d | ||
|
|
36bd9e5a01 | ||
|
|
199f03f627 | ||
|
|
927e8639f3 | ||
|
|
3ff9be2f96 | ||
|
|
ea81b7f598 | ||
|
|
1d1d79f90e |
@@ -3,4 +3,5 @@
|
|||||||
Webapp edition
|
Webapp edition
|
||||||
|
|
||||||
socket messages types: serverInfos, login, leave, userList, createRoom, connectRoom, offer, answer, candidate
|
socket messages types: serverInfos, login, leave, userList, createRoom, connectRoom, offer, answer, candidate
|
||||||
data types: status, vote
|
|
||||||
|
data types: status, vote, settings, userCommand
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
# oozik
|
|
||||||
|
|
||||||
## Project setup
|
|
||||||
```
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
### Compiles and hot-reloads for development
|
|
||||||
```
|
|
||||||
npm run serve
|
|
||||||
```
|
|
||||||
|
|
||||||
### Compiles and minifies for production
|
|
||||||
```
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
### Lints and fixes files
|
|
||||||
```
|
|
||||||
npm run lint
|
|
||||||
```
|
|
||||||
|
|
||||||
### Customize configuration
|
|
||||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
@import "~bulma/sass/utilities/_all";
|
|
||||||
@import "variables";
|
@import "variables";
|
||||||
@import "~bulma";
|
@import "~bulma";
|
||||||
@import "~buefy/src/scss/buefy";
|
@import "~buefy/src/scss/buefy";
|
||||||
@import "overrides";
|
@import "overrides";
|
||||||
// @import "~/node_modules/material-design-icons/iconfont/material-icons.css";
|
|
||||||
@@ -1,23 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<b-button @click="broadcastStatus" icon-left="sync">Force status update</b-button>
|
|
||||||
<hr>
|
|
||||||
<b-table :data="usersList" striped hoverable>
|
<b-table :data="usersList" striped hoverable>
|
||||||
<template slot-scope="props">
|
<template slot-scope="props">
|
||||||
<b-table-column field="name" label="Name">
|
<b-table-column field="name" label="Name">
|
||||||
{{props.row.name}}
|
{{props.row}}
|
||||||
</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-table-column>
|
<b-table-column>
|
||||||
<b-button @click="kickUser(props.row.name)" icon-left="karate" type="is-dark"/>
|
<b-button v-if="isAdmin" @click="kickUser(props.row)" icon-left="karate" type="is-dark"/>
|
||||||
</b-table-column>
|
</b-table-column>
|
||||||
</template>
|
</template>
|
||||||
</b-table>
|
</b-table>
|
||||||
@@ -25,19 +15,21 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { send } from '@/store/signalPlugin'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Admin',
|
name: 'Admin',
|
||||||
computed: {
|
computed: {
|
||||||
usersList () {
|
usersList () {
|
||||||
return this.$store.state.rtc.peers
|
return this.$store.state.room.roomStatus.users
|
||||||
|
},
|
||||||
|
isAdmin () {
|
||||||
|
return this.$store.state.room.admin
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
broadcastStatus () {
|
|
||||||
this.$store.dispatch('rtc/broadcast', { message: this.$store.state.room.roomStatus, type: 'status' })
|
|
||||||
},
|
|
||||||
kickUser (target) {
|
kickUser (target) {
|
||||||
this.$store.dispatch('rtc/kick', target)
|
send({ name: target, type: 'roomUserKick' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="player">
|
||||||
<youtube
|
<youtube
|
||||||
ref="youtube"
|
ref="youtube"
|
||||||
v-bind:fitParent="true"
|
|
||||||
:video-id="roomStatus.current.linkID"
|
:video-id="roomStatus.current.linkID"
|
||||||
:player-vars="playerVars"
|
:player-vars="playerVars"
|
||||||
@playing="roomStatus.player.playing">
|
@playing="roomStatus.player.playing">
|
||||||
</youtube>
|
</youtube>
|
||||||
<hr>
|
<hr>
|
||||||
<b-field position="is-centered">
|
<b-field position="is-centered">
|
||||||
<b-button class="playerButton" @click="play" :icon-right="roomStatus.player.playing ? 'pause' : 'play'"/>
|
<b-button class="playerButton" @click="play" size="is-large" :icon-right="roomStatus.player.playing ? 'pause' : 'play'" :disabled="!(isAdmin || roomSettings.userControl)"/>
|
||||||
<b-button class="playerButton" @click="mute" icon-right="volume-mute"/>
|
<b-button class="playerButton" @click="mute" size="is-large" icon-right="volume-mute"/>
|
||||||
<b-button class="playerButton" @click="skip" icon-right="skip-next" v-if="isAdmin"/>
|
<b-button class="playerButton" @click="skip" size="is-large" icon-right="skip-next" :disabled="!(isAdmin || roomSettings.userControl)"/>
|
||||||
<b-slider class="playerVolume" :min="0" :max="100" :value="100" @change="volume"/>
|
<b-slider class="playerVolume" size="is-large" :min="0" :max="100" :value="100" @change="volume"/>
|
||||||
</b-field>
|
</b-field>
|
||||||
<b-field position="is-centered">
|
<b-field position="is-centered">
|
||||||
<b-slider
|
<b-slider
|
||||||
rounded
|
rounded
|
||||||
|
size="is-medium"
|
||||||
:min="0"
|
:min="0"
|
||||||
:max="roomStatus.player.timeLength"
|
:max="roomStatus.player.timeLength"
|
||||||
:value="roomStatus.player.timeCode"
|
:value="roomStatus.player.timeCode"
|
||||||
:custom-formatter="value => convertTimeCode(value)"
|
:custom-formatter="value => convertTimeCode(value)"
|
||||||
:disabled="!isAdmin"
|
:disabled="!(isAdmin || roomSettings.userControl)"
|
||||||
@change="seek"/>
|
@change="seek"/>
|
||||||
</b-field>
|
</b-field>
|
||||||
<h2 class="subtitle is-6 time">{{convertTimeCode(roomStatus.player.timeCode)}} / {{convertTimeCode(roomStatus.player.timeLength)}}</h2>
|
<h2 class="subtitle is-6 time">{{convertTimeCode(roomStatus.player.timeCode)}} / {{convertTimeCode(roomStatus.player.timeLength)}}</h2>
|
||||||
@@ -29,9 +29,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { send } from '@/store/signalPlugin'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Player',
|
name: 'Player',
|
||||||
props: ['settings'],
|
|
||||||
computed: {
|
computed: {
|
||||||
isAdmin () {
|
isAdmin () {
|
||||||
return this.$store.state.room.admin
|
return this.$store.state.room.admin
|
||||||
@@ -39,6 +40,12 @@ export default {
|
|||||||
roomStatus () {
|
roomStatus () {
|
||||||
return this.$store.state.room.roomStatus
|
return this.$store.state.room.roomStatus
|
||||||
},
|
},
|
||||||
|
roomSettings () {
|
||||||
|
return this.$store.state.room.roomSettings
|
||||||
|
},
|
||||||
|
localSettings () {
|
||||||
|
return this.$store.state.room.localSettings
|
||||||
|
},
|
||||||
player () {
|
player () {
|
||||||
return this.$refs.youtube.player
|
return this.$refs.youtube.player
|
||||||
},
|
},
|
||||||
@@ -58,15 +65,18 @@ export default {
|
|||||||
rel: 0
|
rel: 0
|
||||||
}
|
}
|
||||||
return this.isAdmin ? adminVars : userVars
|
return this.isAdmin ? adminVars : userVars
|
||||||
|
},
|
||||||
|
lastUserCommand () {
|
||||||
|
return this.$store.state.room.lastUserCommand
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this.player.addEventListener('onStateChange', this.playerStateChange)
|
this.player.addEventListener('onStateChange', this.playerStateChange)
|
||||||
// setInterval(this.updateTimeCode, 1000)
|
setInterval(this.updateTimeCode, 1000)
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
roomStatus: function (status) {
|
roomStatus: function (status) {
|
||||||
if (!this.settings.playLink) {
|
if (!this.localSettings.playLink) {
|
||||||
this.player.pauseVideo()
|
this.player.pauseVideo()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -74,12 +84,25 @@ export default {
|
|||||||
this.player.seekTo(status.player.timeCode, true)
|
this.player.seekTo(status.player.timeCode, true)
|
||||||
if (status.player.playing) this.player.playVideo()
|
if (status.player.playing) this.player.playVideo()
|
||||||
else this.player.pauseVideo()
|
else this.player.pauseVideo()
|
||||||
|
},
|
||||||
|
lastUserCommand: function (command) {
|
||||||
|
switch (command.type) {
|
||||||
|
case 'play':
|
||||||
|
this.play()
|
||||||
|
break
|
||||||
|
case 'seek':
|
||||||
|
this.seek(command.argument)
|
||||||
|
break
|
||||||
|
case 'skip':
|
||||||
|
this.skip()
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async playerStateChange (event) {
|
async playerStateChange (event) {
|
||||||
console.log('[PLAYER] Status change ' + event.data)
|
console.log('[PLAYER] Status change ' + event.data)
|
||||||
console.log(await event.target.getVideoData())
|
|
||||||
if (this.isAdmin) {
|
if (this.isAdmin) {
|
||||||
this.$store.dispatch('room/setCurrent', {
|
this.$store.dispatch('room/setCurrent', {
|
||||||
playerStatus: event.data,
|
playerStatus: event.data,
|
||||||
@@ -87,11 +110,18 @@ export default {
|
|||||||
timeLength: await this.player.getDuration(),
|
timeLength: await this.player.getDuration(),
|
||||||
title: await event.target.getVideoData().title
|
title: await event.target.getVideoData().title
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
if (this.roomStatus.player.playing) this.player.playVideo()
|
||||||
|
else this.player.pauseVideo()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
play () {
|
play () {
|
||||||
|
if (this.isAdmin) {
|
||||||
if (this.roomStatus.player.playing) this.player.pauseVideo()
|
if (this.roomStatus.player.playing) this.player.pauseVideo()
|
||||||
else this.player.playVideo()
|
else this.player.playVideo()
|
||||||
|
} else if (this.roomSettings.userControl) {
|
||||||
|
send({ message: { type: 'play' }, type: 'roomUserCommand' })
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async mute () {
|
async mute () {
|
||||||
if (await this.player.isMuted()) this.player.unMute()
|
if (await this.player.isMuted()) this.player.unMute()
|
||||||
@@ -101,13 +131,15 @@ export default {
|
|||||||
this.player.setVolume(volume)
|
this.player.setVolume(volume)
|
||||||
},
|
},
|
||||||
skip () {
|
skip () {
|
||||||
this.$store.commit('room/CURRENT_END')
|
if (this.isAdmin) this.$store.commit('room/CURRENT_END')
|
||||||
|
else if (this.roomSettings.userControl) send({ message: { type: 'skip' }, type: 'roomUserCommand' })
|
||||||
},
|
},
|
||||||
seek (time) {
|
seek (time) {
|
||||||
this.player.seekTo(time, true)
|
if (this.isAdmin) this.player.seekTo(time, true)
|
||||||
|
else if (this.roomSettings.userControl) send({ message: { type: 'seek', argument: time }, type: 'roomUserCommand' })
|
||||||
},
|
},
|
||||||
async updateTimeCode () {
|
async updateTimeCode () {
|
||||||
if (this.settings.playLink) this.$store.dispatch('room/setTimeCode', await this.player.getCurrentTime())
|
if (this.localSettings.playLink) this.$store.dispatch('room/setTimeCode', await this.player.getCurrentTime())
|
||||||
},
|
},
|
||||||
convertTimeCode (timeCode) {
|
convertTimeCode (timeCode) {
|
||||||
var minutes = Math.round(timeCode / 60)
|
var minutes = Math.round(timeCode / 60)
|
||||||
@@ -136,6 +168,10 @@ export default {
|
|||||||
max-width: 150px;
|
max-width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.player {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.time {
|
.time {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<b-button icon-left="plus" @click="addLinkPrompt">Add link</b-button>
|
<b-button v-if="roomSettings.userLink" icon-left="plus" size="is-medium" @click="addLinkPrompt">Add link</b-button>
|
||||||
<hr>
|
<hr>
|
||||||
<b-table :data="roomStatus.playlist" striped hoverable default-sort="vote">
|
<b-table :data="roomStatus.playlist" striped hoverable default-sort="vote">
|
||||||
<template slot-scope="props">
|
<template slot-scope="props">
|
||||||
@@ -21,9 +21,9 @@
|
|||||||
</b-table-column>
|
</b-table-column>
|
||||||
|
|
||||||
<b-table-column>
|
<b-table-column>
|
||||||
<b-button v-if="hasVoted(props.row)" icon-left="arrow-down-bold-outline" type="is-primary" @click="vote (props.row.link, props.row.linkID, false)"/>
|
<b-button v-if="hasVoted(props.row)" icon-left="arrow-down-bold-outline" type="is-primary" size="is-medium" @click="vote (props.row.title, props.row.link, props.row.linkID, false)"/>
|
||||||
<b-button v-else icon-left="arrow-up-bold-outline" type="is-primary" @click="vote (props.row.link, props.row.linkID, true)"/>
|
<b-button v-else icon-left="arrow-up-bold-outline" type="is-primary" size="is-medium" @click="vote (props.row.title, props.row.link, props.row.linkID, true)"/>
|
||||||
<b-button v-if="isAdmin" class="actionButton" icon-left="delete-forever" type="is-danger" @click="removePlay (props.row.linkID)"/>
|
<b-button v-if="isAdmin" class="actionButton" icon-left="delete-forever" type="is-danger" size="is-medium" @click="removePlay (props.row.linkID)"/>
|
||||||
</b-table-column>
|
</b-table-column>
|
||||||
</template>
|
</template>
|
||||||
</b-table>
|
</b-table>
|
||||||
@@ -31,14 +31,19 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { send } from '@/store/signalPlugin'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Palylist',
|
name: 'Playlist',
|
||||||
computed: {
|
computed: {
|
||||||
isAdmin () {
|
isAdmin () {
|
||||||
return this.$store.state.room.admin
|
return this.$store.state.room.admin
|
||||||
},
|
},
|
||||||
roomStatus () {
|
roomStatus () {
|
||||||
return this.$store.state.room.roomStatus
|
return this.$store.state.room.roomStatus
|
||||||
|
},
|
||||||
|
roomSettings () {
|
||||||
|
return this.$store.state.room.roomSettings
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -67,29 +72,30 @@ export default {
|
|||||||
},
|
},
|
||||||
vote (title, link, linkID, isPositive) {
|
vote (title, link, linkID, isPositive) {
|
||||||
if (this.isAdmin) {
|
if (this.isAdmin) {
|
||||||
this.$store.dispatch('room/vote', { title: title, link: link, linkID: linkID, isPositive: isPositive, voterName: this.$store.state.rtc.name })
|
this.$store.dispatch('room/vote', { title: title, link: link, linkID: linkID, isPositive: isPositive, voterName: this.$store.state.app.name })
|
||||||
} else {
|
} else {
|
||||||
this.sendVote(link, linkID, isPositive)
|
this.sendVote(title, link, linkID, isPositive)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sendVote (link, linkID, isPositive) {
|
sendVote (title, link, linkID, isPositive) {
|
||||||
const message = {
|
const message = {
|
||||||
type: 'vote',
|
type: 'vote',
|
||||||
|
title: title,
|
||||||
link: link,
|
link: link,
|
||||||
linkID: linkID,
|
linkID: linkID,
|
||||||
isPositive: isPositive,
|
isPositive: isPositive,
|
||||||
voterName: this.$store.state.rtc.name
|
voterName: this.$store.state.app.name
|
||||||
}
|
}
|
||||||
this.$store.dispatch('rtc/broadcast', { message: message, type: 'vote' })
|
send({ message: message, type: 'roomVote' })
|
||||||
},
|
},
|
||||||
hasVoted (play) {
|
hasVoted (play) {
|
||||||
return play.voters.includes(this.$store.state.rtc.name)
|
return play.voters.includes(this.$store.state.app.name)
|
||||||
},
|
},
|
||||||
removePlay (linkID) {
|
removePlay (linkID) {
|
||||||
this.$store.dispatch('room/removePlay', linkID)
|
this.$store.dispatch('room/removePlay', linkID)
|
||||||
},
|
},
|
||||||
async getInfos (linkID) {
|
async getInfos (linkID) {
|
||||||
const response = await fetch('http://noembed.com/embed?format=json&' + 'url=https://www.youtube.com/watch?v=' + linkID)
|
const response = await fetch('https://noembed.com/embed?format=json&' + 'url=https://www.youtube.com/watch?v=' + linkID)
|
||||||
return await response.json()
|
return await response.json()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
58
client/src/components/Settings.vue
Normal file
58
client/src/components/Settings.vue
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<b-field label="Player">
|
||||||
|
<b-switch size="is-medium" v-model="localSettings.playLink" @input="setLocalSettings">Play link</b-switch>
|
||||||
|
</b-field>
|
||||||
|
<b-field>
|
||||||
|
<b-switch size="is-medium" v-model="localSettings.externalSearch" @input="setLocalSettings">Enable Youtube search</b-switch>
|
||||||
|
</b-field>
|
||||||
|
<div v-if="isAdmin">
|
||||||
|
<hr>
|
||||||
|
<b-field label="Admin">
|
||||||
|
<b-switch size="is-medium" v-model="roomSettings.userControl" @input="setRoomSettings">Users can control video</b-switch>
|
||||||
|
</b-field>
|
||||||
|
<b-field>
|
||||||
|
<b-switch size="is-medium" v-model="roomSettings.userLink" @input="setRoomSettings">Users can add link</b-switch>
|
||||||
|
</b-field>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<b-field label="User">
|
||||||
|
<b-button type="is-danger" icon-right="exit-to-app" @click="leave">Leave</b-button>
|
||||||
|
</b-field>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Settings',
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
localSettings: this.$store.state.room.localSettings,
|
||||||
|
roomSettings: this.$store.state.room.roomSettings
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isAdmin () {
|
||||||
|
return this.$store.state.room.admin
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
if (this.isAdmin) {
|
||||||
|
this.localSettings.playLink = true
|
||||||
|
this.setLocalSettings()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
leave () {
|
||||||
|
this.$store.dispatch('room/leave')
|
||||||
|
this.$router.push({ name: 'Home' })
|
||||||
|
},
|
||||||
|
setLocalSettings () {
|
||||||
|
this.$store.dispatch('room/setLocalSettings', this.localSettings)
|
||||||
|
},
|
||||||
|
setRoomSettings () {
|
||||||
|
this.$store.dispatch('room/setRoomSettings', this.roomSettings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
|
|
||||||
import { register } from 'register-service-worker'
|
import { register } from 'register-service-worker'
|
||||||
|
import { ToastProgrammatic as Toast } from 'buefy'
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
register(`${process.env.BASE_URL}service-worker.js`, {
|
register(`${process.env.BASE_URL}service-worker.js`, {
|
||||||
@@ -18,13 +19,16 @@ if (process.env.NODE_ENV === 'production') {
|
|||||||
},
|
},
|
||||||
updatefound () {
|
updatefound () {
|
||||||
console.log('New content is downloading.')
|
console.log('New content is downloading.')
|
||||||
|
Toast.open('Update found, downloading...')
|
||||||
},
|
},
|
||||||
updated () {
|
updated () {
|
||||||
console.log('New content is available; please refresh.')
|
console.log('New content is available; please refresh.')
|
||||||
window.location.reload(true)
|
window.location.reload(true)
|
||||||
|
Toast.open('App updated !')
|
||||||
},
|
},
|
||||||
offline () {
|
offline () {
|
||||||
console.log('No internet connection found. App is running in offline mode.')
|
console.log('No internet connection found. App is running in offline mode.')
|
||||||
|
Toast.open('No internet connection !')
|
||||||
},
|
},
|
||||||
error (error) {
|
error (error) {
|
||||||
console.error('Error during service worker registration:', error)
|
console.error('Error during service worker registration:', error)
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import router from '@/router/index'
|
|||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
signalServerConnected: false,
|
signalServerConnected: false,
|
||||||
loginSuccess: false,
|
loginSuccess: null,
|
||||||
|
name: '',
|
||||||
error: null,
|
error: null,
|
||||||
serverStatus: {}
|
serverStatus: {}
|
||||||
}
|
}
|
||||||
@@ -23,23 +24,25 @@ const actions = {
|
|||||||
commit('SIGNAL_ERROR', error)
|
commit('SIGNAL_ERROR', error)
|
||||||
},
|
},
|
||||||
login ({ commit }, success) {
|
login ({ commit }, success) {
|
||||||
if (success) commit('LOGIN_SUCCESS')
|
// le JSON parser veut pas voir le boolean
|
||||||
|
if (success === 'true') commit('LOGIN_SUCCESS')
|
||||||
else commit('LOGIN_ERROR')
|
else commit('LOGIN_ERROR')
|
||||||
},
|
},
|
||||||
|
resetLogin ({ commit }) {
|
||||||
|
commit('LOGIN_RESET')
|
||||||
|
},
|
||||||
serverStatus ({ commit }, serverStatus) {
|
serverStatus ({ commit }, serverStatus) {
|
||||||
commit('SET_SERVERSTATUS', serverStatus)
|
commit('SET_SERVERSTATUS', serverStatus)
|
||||||
},
|
},
|
||||||
createRoom ({ commit, dispatch }, code) {
|
|
||||||
commit('CREATE_ROOM')
|
|
||||||
dispatch('room/setRoomCode', code, { root: true })
|
|
||||||
dispatch('room/setAdmin', null, { root: true })
|
|
||||||
},
|
|
||||||
connectRoom ({ commit, dispatch }, name) {
|
connectRoom ({ commit, dispatch }, name) {
|
||||||
commit('CONNECT_ROOM')
|
commit('CONNECT_ROOM')
|
||||||
dispatch('rtc/makeOffer', name, { root: true })
|
// dispatch('rtc/makeOffer', name, { root: true })
|
||||||
},
|
},
|
||||||
error ({ commit }, error) {
|
error ({ commit }, error) {
|
||||||
commit('ERROR', error)
|
commit('ERROR', error)
|
||||||
|
},
|
||||||
|
setName ({ commit }, name) {
|
||||||
|
commit('SET_NAME', name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,18 +60,21 @@ const mutations = {
|
|||||||
LOGIN_ERROR (state) {
|
LOGIN_ERROR (state) {
|
||||||
state.loginSuccess = false
|
state.loginSuccess = false
|
||||||
},
|
},
|
||||||
|
LOGIN_RESET (state) {
|
||||||
|
state.loginSuccess = null
|
||||||
|
},
|
||||||
SET_SERVERSTATUS (state, serverStatus) {
|
SET_SERVERSTATUS (state, serverStatus) {
|
||||||
state.serverStatus = serverStatus
|
state.serverStatus = serverStatus
|
||||||
},
|
},
|
||||||
CREATE_ROOM (state) {
|
|
||||||
router.push({ name: 'Room' })
|
|
||||||
},
|
|
||||||
CONNECT_ROOM (state) {
|
CONNECT_ROOM (state) {
|
||||||
router.push({ name: 'Room' })
|
router.push({ name: 'Room' })
|
||||||
},
|
},
|
||||||
ERROR (state, error) {
|
ERROR (state, error) {
|
||||||
state.error = error
|
state.error = error
|
||||||
Dialog.alert(error)
|
Dialog.alert(error)
|
||||||
|
},
|
||||||
|
SET_NAME (state, name) {
|
||||||
|
state.name = name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
import signal from './signalPlugin'
|
import signal from './signalPlugin'
|
||||||
import rtc from './rtcModule'
|
|
||||||
import room from './roomModule'
|
import room from './roomModule'
|
||||||
import app from './appModule'
|
import app from './appModule'
|
||||||
|
|
||||||
@@ -9,7 +8,6 @@ Vue.use(Vuex)
|
|||||||
|
|
||||||
export default new Vuex.Store({
|
export default new Vuex.Store({
|
||||||
modules: {
|
modules: {
|
||||||
rtc,
|
|
||||||
app,
|
app,
|
||||||
room
|
room
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { send } from './signalPlugin'
|
||||||
|
import router from '@/router/index'
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
admin: false,
|
admin: false,
|
||||||
roomStatus: {
|
roomStatus: {
|
||||||
@@ -15,14 +18,31 @@ const state = {
|
|||||||
votes: 0,
|
votes: 0,
|
||||||
voters: []
|
voters: []
|
||||||
},
|
},
|
||||||
playlist: []
|
playlist: [],
|
||||||
}
|
users: []
|
||||||
|
},
|
||||||
|
roomSettings: {
|
||||||
|
userControl: false,
|
||||||
|
userLink: true
|
||||||
|
},
|
||||||
|
localSettings: {
|
||||||
|
playLink: false,
|
||||||
|
externalSearch: false
|
||||||
|
},
|
||||||
|
lastUserCommand: null
|
||||||
}
|
}
|
||||||
|
|
||||||
const getters = {
|
const getters = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
|
createRoom ({ commit }, roomCode) {
|
||||||
|
commit('SET_ROOMCODE', roomCode)
|
||||||
|
commit('SET_ADMIN')
|
||||||
|
send({ message: state.roomSettings, type: 'roomSettings' })
|
||||||
|
send({ message: state.roomStatus, type: 'roomStatus' })
|
||||||
|
router.push({ name: 'Room' })
|
||||||
|
},
|
||||||
setRoomCode ({ commit }, roomCode) {
|
setRoomCode ({ commit }, roomCode) {
|
||||||
commit('SET_ROOMCODE', roomCode)
|
commit('SET_ROOMCODE', roomCode)
|
||||||
},
|
},
|
||||||
@@ -32,13 +52,23 @@ const actions = {
|
|||||||
setRoomStatus ({ commit }, roomStatus) {
|
setRoomStatus ({ commit }, roomStatus) {
|
||||||
commit('SET_ROOMSTATUS', roomStatus)
|
commit('SET_ROOMSTATUS', roomStatus)
|
||||||
},
|
},
|
||||||
|
setUserCommand ({ commit }, command) {
|
||||||
|
commit('SET_USERCOMMAND', command)
|
||||||
|
},
|
||||||
|
setRoomSettings ({ commit, state }, roomSettings) {
|
||||||
|
commit('SET_ROOMSETTINGS', roomSettings)
|
||||||
|
if (state.admin) send({ message: state.roomSettings, type: 'roomSettings' })
|
||||||
|
},
|
||||||
|
setLocalSettings ({ commit }, localSettings) {
|
||||||
|
commit('SET_LOCALSETTINGS', localSettings)
|
||||||
|
},
|
||||||
setAdmin ({ commit }) {
|
setAdmin ({ commit }) {
|
||||||
commit('SET_ADMIN')
|
commit('SET_ADMIN')
|
||||||
},
|
},
|
||||||
setTimeCode ({ commit }, timeCode) {
|
setTimeCode ({ commit }, timeCode) {
|
||||||
commit('SET_TIMECODE', timeCode)
|
commit('SET_TIMECODE', timeCode)
|
||||||
},
|
},
|
||||||
vote ({ commit, dispatch, state }, { title, link, linkID, isPositive, voterName }) {
|
vote ({ commit, state }, { title, link, linkID, isPositive, voterName }) {
|
||||||
console.log('vote on ' + link + ' | ' + linkID + ' (' + isPositive + ') by ' + voterName)
|
console.log('vote on ' + link + ' | ' + linkID + ' (' + isPositive + ') by ' + voterName)
|
||||||
if (isPositive) {
|
if (isPositive) {
|
||||||
commit('ADD_VOTE', {
|
commit('ADD_VOTE', {
|
||||||
@@ -53,13 +83,13 @@ const actions = {
|
|||||||
voterName: voterName
|
voterName: voterName
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
dispatch('rtc/broadcast', { message: state.roomStatus, type: 'status' }, { root: true })
|
send({ message: state.roomStatus, type: 'roomStatus' })
|
||||||
},
|
},
|
||||||
removePlay ({ commit, dispatch, state }, linkID) {
|
removePlay ({ commit, state }, linkID) {
|
||||||
commit('REMOVE_PLAY', linkID)
|
commit('REMOVE_PLAY', linkID)
|
||||||
dispatch('rtc/broadcast', { message: state.roomStatus, type: 'status' }, { root: true })
|
send({ message: state.roomStatus, type: 'roomStatus' })
|
||||||
},
|
},
|
||||||
setCurrent ({ commit, dispatch }, { playerStatus, timeCode, timeLength, title }) {
|
setCurrent ({ commit }, { playerStatus, timeCode, timeLength, title }) {
|
||||||
switch (playerStatus) {
|
switch (playerStatus) {
|
||||||
case 0:
|
case 0:
|
||||||
commit('CURRENT_END')
|
commit('CURRENT_END')
|
||||||
@@ -72,11 +102,13 @@ const actions = {
|
|||||||
case 2:
|
case 2:
|
||||||
commit('CURRENT_PAUSE', timeCode)
|
commit('CURRENT_PAUSE', timeCode)
|
||||||
break
|
break
|
||||||
|
default:
|
||||||
|
return
|
||||||
}
|
}
|
||||||
dispatch('rtc/broadcast', { message: state.roomStatus, type: 'status' }, { root: true })
|
send({ message: state.roomStatus, type: 'roomStatus' })
|
||||||
},
|
},
|
||||||
leave ({ commit, dispatch }) {
|
leave ({ commit }) {
|
||||||
dispatch('rtc/leave', null, { root: true })
|
send({ type: 'roomLeave' })
|
||||||
commit('SET_ROOMSTATUS', {
|
commit('SET_ROOMSTATUS', {
|
||||||
roomName: '',
|
roomName: '',
|
||||||
roomCode: '',
|
roomCode: '',
|
||||||
@@ -94,6 +126,7 @@ const actions = {
|
|||||||
},
|
},
|
||||||
playlist: []
|
playlist: []
|
||||||
})
|
})
|
||||||
|
router.push({ name: 'Home' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,6 +140,12 @@ const mutations = {
|
|||||||
SET_ROOMSTATUS (state, roomStatus) {
|
SET_ROOMSTATUS (state, roomStatus) {
|
||||||
state.roomStatus = roomStatus
|
state.roomStatus = roomStatus
|
||||||
},
|
},
|
||||||
|
SET_ROOMSETTINGS (state, roomSettings) {
|
||||||
|
state.roomSettings = roomSettings
|
||||||
|
},
|
||||||
|
SET_LOCALSETTINGS (state, localSettings) {
|
||||||
|
state.localSettings = localSettings
|
||||||
|
},
|
||||||
SET_ADMIN (state) {
|
SET_ADMIN (state) {
|
||||||
state.admin = true
|
state.admin = true
|
||||||
},
|
},
|
||||||
@@ -119,6 +158,9 @@ const mutations = {
|
|||||||
SET_CURRENTTITLE (state, title) {
|
SET_CURRENTTITLE (state, title) {
|
||||||
state.roomStatus.current.title = title
|
state.roomStatus.current.title = title
|
||||||
},
|
},
|
||||||
|
SET_USERCOMMAND (state, command) {
|
||||||
|
state.lastUserCommand = command
|
||||||
|
},
|
||||||
ADD_VOTE (state, { title, link, linkID, voterName }) {
|
ADD_VOTE (state, { title, link, linkID, voterName }) {
|
||||||
var play = state.roomStatus.playlist.find(play => play.linkID === linkID)
|
var play = state.roomStatus.playlist.find(play => play.linkID === linkID)
|
||||||
if (play === undefined) {
|
if (play === undefined) {
|
||||||
|
|||||||
@@ -1,212 +0,0 @@
|
|||||||
import { send } from './signalPlugin'
|
|
||||||
import store from './index'
|
|
||||||
|
|
||||||
var lastPeer // horrible
|
|
||||||
|
|
||||||
const configuration = {
|
|
||||||
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
|
|
||||||
}
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
name: null,
|
|
||||||
peers: []
|
|
||||||
}
|
|
||||||
|
|
||||||
const getters = {
|
|
||||||
displayName: state => state.name
|
|
||||||
}
|
|
||||||
|
|
||||||
const actions = {
|
|
||||||
setName ({ commit }, name) {
|
|
||||||
commit('SET_NAME', name)
|
|
||||||
},
|
|
||||||
async makeOffer ({ commit }, targetName) {
|
|
||||||
commit('CREATE_PEER_CONNECTION', targetName)
|
|
||||||
commit('OFFER', targetName)
|
|
||||||
},
|
|
||||||
async offer ({ commit }, { offer, senderName }) {
|
|
||||||
console.log('offer from ' + senderName)
|
|
||||||
commit('CREATE_PEER_CONNECTION', senderName)
|
|
||||||
commit('ANSWER', { target: senderName, offer: offer })
|
|
||||||
},
|
|
||||||
answer ({ commit }, { answer, senderName }) {
|
|
||||||
console.log('answer from ' + senderName)
|
|
||||||
commit('FINALIZE', { target: senderName, answer: answer })
|
|
||||||
},
|
|
||||||
candidate ({ commit }, { candidate, senderName }) {
|
|
||||||
commit('CANDIDATE', { target: senderName, candidate: candidate })
|
|
||||||
},
|
|
||||||
leave ({ commit }) {
|
|
||||||
commit('LEAVE')
|
|
||||||
},
|
|
||||||
kick ({ commit }, target) {
|
|
||||||
commit('KICK', target)
|
|
||||||
},
|
|
||||||
broadcast ({ commit }, { message, type }) {
|
|
||||||
commit('BROADCAST', { message: message, type: type })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const mutations = {
|
|
||||||
SET_NAME (state, name) {
|
|
||||||
state.name = name
|
|
||||||
},
|
|
||||||
CREATE_PEER_CONNECTION (state, target) {
|
|
||||||
console.log('[RTC] create peer connection with ' + target)
|
|
||||||
var peer = {
|
|
||||||
name: target,
|
|
||||||
connection: new RTCPeerConnection(configuration),
|
|
||||||
dataChannel: null
|
|
||||||
}
|
|
||||||
|
|
||||||
state.peers.push(peer)
|
|
||||||
|
|
||||||
peer.connection.onicecandidate = function (event) {
|
|
||||||
if (event.candidate) {
|
|
||||||
send({
|
|
||||||
type: 'candidate',
|
|
||||||
name: state.name,
|
|
||||||
target: target,
|
|
||||||
data: event.candidate
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
peer.connection.onnegotiationneeded = function () { handleNegotiationNeededEvent(target) }
|
|
||||||
peer.connection.onsignalingstatechange = function () { handleSignalingStateChangeEvent(peer.connection) }
|
|
||||||
peer.connection.oniceconnectionstatechange = function () { handleICEConnectionStateChangeEvent(peer.connection) }
|
|
||||||
peer.connection.onicegatheringstatechange = function () { handleICEGatheringStateChangeEvent(peer.connection) }
|
|
||||||
},
|
|
||||||
async OFFER (state, target) {
|
|
||||||
console.log('[RTC] make offer to ' + target)
|
|
||||||
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
|
|
||||||
|
|
||||||
var offer = await peer.connection.createOffer()
|
|
||||||
send({
|
|
||||||
type: 'offer',
|
|
||||||
name: state.name,
|
|
||||||
target: target,
|
|
||||||
data: offer
|
|
||||||
})
|
|
||||||
await peer.connection.setLocalDescription(offer)
|
|
||||||
},
|
|
||||||
async ANSWER (state, { target, offer }) {
|
|
||||||
console.log('[RTC] answer to ' + target)
|
|
||||||
var peer = state.peers.find(peer => peer.name === target)
|
|
||||||
|
|
||||||
// Permet d'associer le datachannel de la callback au bon peer
|
|
||||||
lastPeer = peer
|
|
||||||
|
|
||||||
peer.connection.ondatachannel = handleDataChannelCallback
|
|
||||||
|
|
||||||
await peer.connection.setRemoteDescription(new RTCSessionDescription(offer))
|
|
||||||
|
|
||||||
var answer = await peer.connection.createAnswer()
|
|
||||||
|
|
||||||
await peer.connection.setLocalDescription(answer)
|
|
||||||
|
|
||||||
send({
|
|
||||||
type: 'answer',
|
|
||||||
name: state.name,
|
|
||||||
target: target,
|
|
||||||
data: answer
|
|
||||||
})
|
|
||||||
},
|
|
||||||
FINALIZE (state, { target, answer }) {
|
|
||||||
var peer = state.peers.find(peer => peer.name === target)
|
|
||||||
peer.connection.setRemoteDescription(new RTCSessionDescription(answer))
|
|
||||||
},
|
|
||||||
CANDIDATE (state, { target, candidate }) {
|
|
||||||
var peer = state.peers.find(peer => peer.name === target)
|
|
||||||
peer.connection.addIceCandidate(new RTCIceCandidate(candidate))
|
|
||||||
},
|
|
||||||
LEAVE (state) {
|
|
||||||
state.peers.forEach((peer) => {
|
|
||||||
peer.dataChannel.close()
|
|
||||||
peer.connection.close()
|
|
||||||
})
|
|
||||||
state.peers = []
|
|
||||||
},
|
|
||||||
KICK (state, target) {
|
|
||||||
var peer = state.peers.find(peer => peer.name === target)
|
|
||||||
peer.dataChannel.close()
|
|
||||||
peer.connection.close()
|
|
||||||
const index = state.peers.indexOf(peer)
|
|
||||||
if (index > -1) {
|
|
||||||
state.peers.splice(index, 1)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
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(data)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleICEConnectionStateChangeEvent (connection) {
|
|
||||||
console.log('[RTC] ice connection change to ' + connection.iceConnectionState)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleICEGatheringStateChangeEvent (connection) {
|
|
||||||
console.log('[RTC] ice gathering change to ' + connection.iceGatheringState)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleNegotiationNeededEvent (target) {
|
|
||||||
console.log('[RTC] negotiation needed from ' + target)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSignalingStateChangeEvent (connection) {
|
|
||||||
console.log('[RTC] state changed to ' + connection.signalingState)
|
|
||||||
switch (connection.signalingState) {
|
|
||||||
case 'closed':
|
|
||||||
await connection.close()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDataChannelStateChangeEvent () {
|
|
||||||
console.log('[RTC] data channel state change')
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDataChannelCallback (event) {
|
|
||||||
console.log('[RTC] data channel callback ' + event + ' target ' + event.target)
|
|
||||||
var peer = lastPeer
|
|
||||||
peer.dataChannel = event.channel
|
|
||||||
peer.dataChannel.onmessage = handleDataChannelMessage
|
|
||||||
peer.dataChannel.onopen = handleDataChannelStateChangeEvent
|
|
||||||
peer.dataChannel.onclose = handleDataChannelStateChangeEvent
|
|
||||||
|
|
||||||
store.dispatch('rtc/broadcast', { message: store.state.room.roomStatus, type: 'status' })
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDataChannelMessage (event) {
|
|
||||||
console.log('[RTC] data channel message ' + 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, linkID: data.message.linkID, isPositive: data.message.isPositive, voterName: data.message.voterName })
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
namespaced: true,
|
|
||||||
state,
|
|
||||||
getters,
|
|
||||||
actions,
|
|
||||||
mutations
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,25 @@
|
|||||||
const connection = new WebSocket('ws://localhost:8181/socket')
|
// const connection = new WebSocket('ws://localhost:8181/socket')
|
||||||
// const connection = new WebSocket('wss://echo.websocket.org')
|
// const connection = new WebSocket('wss://echo.websocket.org')
|
||||||
|
const connection = new WebSocket('wss://voozik.gltronic.ovh/socket')
|
||||||
|
|
||||||
|
var alive = null
|
||||||
|
|
||||||
export default function createSignalPlugin () {
|
export default function createSignalPlugin () {
|
||||||
return store => {
|
return store => {
|
||||||
connection.onopen = function () {
|
connection.onopen = function () {
|
||||||
console.log('[WS] connected')
|
console.log('[WS] connected')
|
||||||
store.dispatch('app/signalConnected')
|
store.dispatch('app/signalConnected')
|
||||||
|
alive = setTimeout(send({ type: 'alive' }), 5000)
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.onclose = function () {
|
||||||
|
clearTimeout(alive)
|
||||||
}
|
}
|
||||||
|
|
||||||
connection.onerror = function (error) {
|
connection.onerror = function (error) {
|
||||||
console.log('[WS] error ' + error)
|
console.log('[WS] error ' + error.type)
|
||||||
store.dispatch('app/signalError', error)
|
clearTimeout(alive)
|
||||||
|
store.dispatch('app/signalError', error.type)
|
||||||
}
|
}
|
||||||
|
|
||||||
connection.onmessage = function (message) {
|
connection.onmessage = function (message) {
|
||||||
@@ -19,22 +28,7 @@ export default function createSignalPlugin () {
|
|||||||
var data = JSON.parse(message.data)
|
var data = JSON.parse(message.data)
|
||||||
|
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case 'offer':
|
|
||||||
console.log('offer from ' + data.name)
|
|
||||||
store.dispatch('rtc/offer', { offer: data.data, senderName: data.name })
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'answer':
|
|
||||||
console.log('answer from ' + data.name)
|
|
||||||
store.dispatch('rtc/answer', { answer: data.data, senderName: data.name })
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'candidate':
|
|
||||||
store.dispatch('rtc/candidate', { candidate: data.data, senderName: data.name })
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'leave':
|
case 'leave':
|
||||||
store.dispatch('rtc/leave')
|
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'login':
|
case 'login':
|
||||||
@@ -42,14 +36,14 @@ export default function createSignalPlugin () {
|
|||||||
break
|
break
|
||||||
|
|
||||||
case 'serverInfos':
|
case 'serverInfos':
|
||||||
store.dispatch('app/serverStatus', data)
|
store.dispatch('app/serverStatus', data.message)
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'createRoom':
|
case 'roomCreate':
|
||||||
store.dispatch('app/createRoom', data.message)
|
store.dispatch('room/createRoom', data.message)
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'connectRoom':
|
case 'roomConnect':
|
||||||
store.dispatch('app/connectRoom', data.message)
|
store.dispatch('app/connectRoom', data.message)
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -57,6 +51,26 @@ export default function createSignalPlugin () {
|
|||||||
store.dispatch('app/error', data.message)
|
store.dispatch('app/error', data.message)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
case 'roomStatus':
|
||||||
|
if (store.state.room.admin) store.dispatch('room/setRoomStatus', data.message)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'roomSettings':
|
||||||
|
if (!store.state.room.admin) store.dispatch('room/setRoomSettings', data.message)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'roomVote':
|
||||||
|
store.dispatch('room/vote', { link: data.message.link, linkID: data.message.linkID, isPositive: data.message.isPositive, voterName: data.message.voterName })
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'roomUserCommand':
|
||||||
|
if (store.state.room.roomSettings.userControl) store.dispatch('room/setUserCommand', data.message)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'roomUserKick':
|
||||||
|
store.dispatch('room/leave', data.message)
|
||||||
|
break
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="home">
|
<div class="home">
|
||||||
|
<img src="../assets/logo.png" alt="voozik logo">
|
||||||
<div v-if="serverConnected">
|
<div v-if="serverConnected">
|
||||||
<h1 class="title is-1 has-text-success">Server online</h1>
|
<h1 class="title is-1 has-text-success">Server online</h1>
|
||||||
<div v-if="isLoggedIn">
|
<div v-if="isLoggedIn">
|
||||||
@@ -7,13 +8,17 @@
|
|||||||
<h1 class="subtitle is-6">Connected as {{userName}}</h1>
|
<h1 class="subtitle is-6">Connected as {{userName}}</h1>
|
||||||
<hr>
|
<hr>
|
||||||
<b-field position="is-centered">
|
<b-field position="is-centered">
|
||||||
<b-button type="is-primary" @click="connectToRoomPrompt">Join a room</b-button>
|
<b-button type="is-primary" size="is-large" @click="connectToRoomPrompt">Join a room</b-button>
|
||||||
</b-field>
|
</b-field>
|
||||||
<b-field position="is-centered">
|
<b-field position="is-centered">
|
||||||
<b-button @click="isQRModalActive = true" icon-right="qrcode" size="is-large"/>
|
<b-button size="is-large" icon-right="qrcode" @click="isQRModalActive = true" />
|
||||||
</b-field>
|
</b-field>
|
||||||
<hr>
|
<hr>
|
||||||
<b-button type="is-primary" @click="makeRoomPrompt">Make a room</b-button>
|
<b-button type="is-primary" size="is-large" @click="makeRoomPrompt">Make a room</b-button>
|
||||||
|
<hr>
|
||||||
|
<b-button @click="changeName">Change name</b-button>
|
||||||
|
<hr>
|
||||||
|
<p>V2.0</p>
|
||||||
<b-modal :active.sync="isQRModalActive" has-modal-card trap-focus>
|
<b-modal :active.sync="isQRModalActive" has-modal-card trap-focus>
|
||||||
<QRReader v-on:get-code="connectToRoom"/>
|
<QRReader v-on:get-code="connectToRoom"/>
|
||||||
</b-modal>
|
</b-modal>
|
||||||
@@ -51,12 +56,28 @@ export default {
|
|||||||
return this.$store.state.app.loginSuccess
|
return this.$store.state.app.loginSuccess
|
||||||
},
|
},
|
||||||
userName () {
|
userName () {
|
||||||
return this.$store.state.rtc.name
|
return this.$store.state.app.name
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
serverConnected: function (isConnected) {
|
serverConnected: async function (isConnected) {
|
||||||
if (!this.isLoggedIn && this.serverConnected) this.loginPrompt()
|
if (!this.isLoggedIn && this.serverConnected) {
|
||||||
|
setTimeout(send({ type: 'alive' }), 5000)
|
||||||
|
const name = await localStorage.getItem('name')
|
||||||
|
if (name) this.login(name)
|
||||||
|
else this.loginPrompt()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isLoggedIn: function (success) {
|
||||||
|
// a cause de null = false
|
||||||
|
if (success === false) {
|
||||||
|
this.$buefy.toast.open({
|
||||||
|
message: 'Invalid login',
|
||||||
|
type: 'is-danger'
|
||||||
|
})
|
||||||
|
this.$store.dispatch('app/resetLogin')
|
||||||
|
this.loginPrompt()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -71,7 +92,10 @@ export default {
|
|||||||
maxlength: 30
|
maxlength: 30
|
||||||
},
|
},
|
||||||
confirmText: 'KK',
|
confirmText: 'KK',
|
||||||
onConfirm: (name) => this.login(name)
|
onConfirm: (name) => {
|
||||||
|
localStorage.setItem('name', name)
|
||||||
|
this.login(name)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
login (name) {
|
login (name) {
|
||||||
@@ -79,7 +103,7 @@ export default {
|
|||||||
type: 'login',
|
type: 'login',
|
||||||
name: name
|
name: name
|
||||||
})
|
})
|
||||||
this.$store.dispatch('rtc/setName', name)
|
this.$store.dispatch('app/setName', name)
|
||||||
},
|
},
|
||||||
makeRoomPrompt () {
|
makeRoomPrompt () {
|
||||||
this.$buefy.dialog.prompt({
|
this.$buefy.dialog.prompt({
|
||||||
@@ -97,8 +121,7 @@ export default {
|
|||||||
},
|
},
|
||||||
makeRoom (name) {
|
makeRoom (name) {
|
||||||
send({
|
send({
|
||||||
type: 'createRoom',
|
type: 'roomCreate'
|
||||||
name: name
|
|
||||||
})
|
})
|
||||||
this.$store.dispatch('room/setRoomName', name)
|
this.$store.dispatch('room/setRoomName', name)
|
||||||
},
|
},
|
||||||
@@ -118,9 +141,13 @@ export default {
|
|||||||
},
|
},
|
||||||
connectToRoom (code) {
|
connectToRoom (code) {
|
||||||
send({
|
send({
|
||||||
type: 'connectRoom',
|
type: 'roomConnect',
|
||||||
name: code
|
code: code
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
changeName () {
|
||||||
|
localStorage.removeItem('name')
|
||||||
|
this.loginPrompt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="room container">
|
<div class="room container">
|
||||||
|
<div class="roomTitle">
|
||||||
<h1 class="title is-1">{{roomStatus.roomName}}</h1>
|
<h1 class="title is-1">{{roomStatus.roomName}}</h1>
|
||||||
<h2 class="subtitle">{{roomStatus.current.title}}</h2>
|
<h2 class="subtitle">{{roomStatus.current.title}}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
<b-tabs>
|
<b-tabs type="is-boxed" expanded>
|
||||||
<b-tab-item label="Playlist">
|
<b-tab-item label="Playlist" icon="playlist-play">
|
||||||
<Playlist />
|
<Playlist />
|
||||||
</b-tab-item>
|
</b-tab-item>
|
||||||
|
|
||||||
<b-tab-item label="Player" :visible="settings.showPlayer && settings.playLink">
|
<b-tab-item label="Player" icon="youtube" :visible="localSettings.playLink">
|
||||||
<Player v-bind:settings="settings"/>
|
<Player />
|
||||||
</b-tab-item>
|
</b-tab-item>
|
||||||
|
|
||||||
<b-tab-item label="Admin" :visible="isAdmin">
|
<b-tab-item label="Users" icon="account-multiple">
|
||||||
<Admin />
|
<Admin />
|
||||||
</b-tab-item>
|
</b-tab-item>
|
||||||
|
|
||||||
<b-tab-item label="Invite">
|
<b-tab-item label="Invite" icon="qrcode">
|
||||||
<Invite v-bind:roomCode="roomStatus.roomCode"/>
|
<Invite v-bind:roomCode="roomStatus.roomCode"/>
|
||||||
</b-tab-item>
|
</b-tab-item>
|
||||||
|
|
||||||
<b-tab-item label="Settings">
|
<b-tab-item label="Settings" icon="cog">
|
||||||
<b-field label="Player">
|
<Settings />
|
||||||
<b-switch v-model="settings.playLink">Play link</b-switch>
|
|
||||||
</b-field>
|
|
||||||
<b-field>
|
|
||||||
<b-switch v-model="settings.showPlayer" :disabled="!settings.playLink">Show player</b-switch>
|
|
||||||
</b-field>
|
|
||||||
<hr>
|
|
||||||
<b-field label="User">
|
|
||||||
<b-button type="is-danger" icon-right="exit-to-app" @click="leave">Leave</b-button>
|
|
||||||
</b-field>
|
|
||||||
</b-tab-item>
|
</b-tab-item>
|
||||||
|
|
||||||
</b-tabs>
|
</b-tabs>
|
||||||
@@ -43,6 +36,7 @@ import Invite from './../components/Invite'
|
|||||||
import Admin from './../components/Admin'
|
import Admin from './../components/Admin'
|
||||||
import Playlist from './../components/Playlist'
|
import Playlist from './../components/Playlist'
|
||||||
import Player from './../components/Player'
|
import Player from './../components/Player'
|
||||||
|
import Settings from './../components/Settings'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Room',
|
name: 'Room',
|
||||||
@@ -50,42 +44,25 @@ export default {
|
|||||||
Invite,
|
Invite,
|
||||||
Admin,
|
Admin,
|
||||||
Playlist,
|
Playlist,
|
||||||
Player
|
Player,
|
||||||
|
Settings
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
roomStatus () {
|
roomStatus () {
|
||||||
return this.$store.state.room.roomStatus
|
return this.$store.state.room.roomStatus
|
||||||
},
|
},
|
||||||
isAdmin () {
|
|
||||||
return this.$store.state.room.admin
|
|
||||||
},
|
|
||||||
isLoggedIn () {
|
isLoggedIn () {
|
||||||
return this.$store.state.app.loginSuccess
|
return this.$store.state.app.loginSuccess
|
||||||
},
|
},
|
||||||
isRoomLoading () {
|
isRoomLoading () {
|
||||||
return this.roomStatus.roomName === ''
|
return this.roomStatus.roomName === ''
|
||||||
}
|
|
||||||
},
|
},
|
||||||
data () {
|
localSettings () {
|
||||||
return {
|
return this.$store.state.room.localSettings
|
||||||
settings: {
|
|
||||||
playLink: false,
|
|
||||||
showPlayer: false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
if (!this.isLoggedIn) this.$router.push({ name: 'Home' })
|
if (!this.isLoggedIn) this.$router.push({ name: 'Home' })
|
||||||
if (this.isAdmin) {
|
|
||||||
this.settings.playLink = true
|
|
||||||
this.settings.showPlayer = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
leave () {
|
|
||||||
this.$store.dispatch('room/leave')
|
|
||||||
this.$router.push({ name: 'Home' })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -94,4 +71,9 @@ export default {
|
|||||||
.room {
|
.room {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.roomTitle {
|
||||||
|
padding-bottom: 30px;
|
||||||
|
padding-left: 25px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
pwa: {
|
pwa: {
|
||||||
name: 'Voozik',
|
name: 'Voozik',
|
||||||
themeColor: '#26a8a2',
|
themeColor: '#1f2424',
|
||||||
msTileColor: '#26a8a2',
|
msTileColor: '#1f2424',
|
||||||
appleMobileWebAppCapable: 'yes',
|
appleMobileWebAppCapable: 'yes',
|
||||||
appleMobileWebAppStatusBarStyle: 'black',
|
appleMobileWebAppStatusBarStyle: 'black',
|
||||||
workboxOptions: {
|
workboxOptions: {
|
||||||
|
|||||||
@@ -7,10 +7,18 @@ import org.springframework.web.socket.WebSocketSession;
|
|||||||
|
|
||||||
public interface IRoomManager {
|
public interface IRoomManager {
|
||||||
public void login(WebSocketSession session, String name) throws InterruptedException, IOException;
|
public void login(WebSocketSession session, String name) throws InterruptedException, IOException;
|
||||||
public void leave(WebSocketSession session);
|
public void leave(WebSocketSession session) throws InterruptedException, IOException;
|
||||||
|
|
||||||
public void createRoom(WebSocketSession session) throws InterruptedException, IOException;
|
public void createRoom(WebSocketSession session) throws InterruptedException, IOException;
|
||||||
public void connectRoom(WebSocketSession session, String roomName) throws InterruptedException, IOException;
|
public void connectRoom(WebSocketSession session, String roomCode) throws InterruptedException, IOException;
|
||||||
public void followRTC(WebSocketSession session, TextMessage message) throws InterruptedException, IOException;
|
public void leaveRoom(WebSocketSession session) throws InterruptedException, IOException;
|
||||||
public void sendMessage(WebSocketSession session, String type, String message) throws InterruptedException, IOException;
|
|
||||||
|
public void setRoomStatus(WebSocketSession session, TextMessage message) throws InterruptedException, IOException;
|
||||||
|
public void setRoomSettings(WebSocketSession session, TextMessage message) throws InterruptedException, IOException;
|
||||||
|
|
||||||
|
public void forwardRoomMessage(WebSocketSession session, TextMessage message) throws InterruptedException, IOException;
|
||||||
|
|
||||||
|
public void kickUser(WebSocketSession session, String username) throws InterruptedException, IOException;
|
||||||
|
|
||||||
public void sendServerInfos(WebSocketSession session) throws InterruptedException, IOException;
|
public void sendServerInfos(WebSocketSession session) throws InterruptedException, IOException;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package gltronic.voozik.business;
|
||||||
|
|
||||||
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
|
|
||||||
|
public interface IYTSearch {
|
||||||
|
public void search (String query, WebSocketSession session);
|
||||||
|
}
|
||||||
@@ -1,117 +1,254 @@
|
|||||||
package gltronic.voozik.business;
|
package gltronic.voozik.business;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.socket.TextMessage;
|
import org.springframework.web.socket.TextMessage;
|
||||||
import org.springframework.web.socket.WebSocketSession;
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
|
|
||||||
import gltronic.voozik.model.BiMap;
|
import gltronic.voozik.model.Server;
|
||||||
|
import gltronic.voozik.model.ServerStatus;
|
||||||
|
import gltronic.voozik.model.VRoom;
|
||||||
|
import gltronic.voozik.model.VUser;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class RoomManager implements IRoomManager{
|
public class RoomManager implements IRoomManager {
|
||||||
BiMap<String, WebSocketSession> users = new BiMap<String, WebSocketSession>();
|
@Autowired
|
||||||
BiMap<String, String> rooms = new BiMap<String, String>();
|
Server server;
|
||||||
|
|
||||||
|
@Override
|
||||||
public void login(WebSocketSession session, String name) throws InterruptedException, IOException {
|
public void login(WebSocketSession session, String name) throws InterruptedException, IOException {
|
||||||
if (name == null) {
|
if (name == null) {
|
||||||
sendMessage(session, "error", "bad command command");
|
SocketUtils.sendMessage(session, "error", "bad command command");
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (users.containsKey(name)) {
|
|
||||||
sendMessage(session, "login", "false");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
users.put(name, session);
|
if (server.users.stream().anyMatch(user -> user.getSession().equals(session))) {
|
||||||
sendMessage(session, "login", "true");
|
SocketUtils.sendMessage(session, "login", "false");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (server.users.stream().anyMatch(user -> user.getName().equals(name))) {
|
||||||
|
SocketUtils.sendMessage(session, "login", "false");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VUser user = new VUser();
|
||||||
|
user.setName(name);
|
||||||
|
user.setSession(session);
|
||||||
|
|
||||||
|
server.users.add(user);
|
||||||
|
|
||||||
|
SocketUtils.sendMessage(session, "login", "true");
|
||||||
sendServerInfos(session);
|
sendServerInfos(session);
|
||||||
|
|
||||||
System.err.println("[ROOM] Logged "+name);
|
System.err.println("[ROOM] Logged " + name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void leave(WebSocketSession session) {
|
@Override
|
||||||
String name = users.getKey(session);
|
public void leave(WebSocketSession session) throws InterruptedException, IOException {
|
||||||
users.removeValue(session);
|
VUser user = UserUtils.getUser(session, server.users);
|
||||||
if(rooms.containsValue(name)) rooms.removeValue(name);
|
if (user == null) return;
|
||||||
|
|
||||||
|
server.users.remove(user);
|
||||||
|
Optional<VRoom> oroom = server.rooms.stream().filter(room -> room.getUsers().contains(user)).findFirst();
|
||||||
|
|
||||||
|
if (oroom.isPresent()) {
|
||||||
|
oroom.get().getUsers().remove(user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void createRoom(WebSocketSession session) throws InterruptedException, IOException {
|
public void createRoom(WebSocketSession session) throws InterruptedException, IOException {
|
||||||
if (!users.containsValue(session)) {
|
VUser user = UserUtils.getUser(session, server.users);
|
||||||
sendMessage(session, "error", "need login");
|
if (user == null) return;
|
||||||
return;
|
|
||||||
}
|
if (user.getRoom() != null) {
|
||||||
String userName = users.getKey(session);
|
SocketUtils.sendMessage(session, "error", "no already in room");
|
||||||
if (rooms.containsKey(userName)) {
|
|
||||||
sendMessage(session, "error", "no multiple room");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Random random = new Random();
|
Random random = new Random();
|
||||||
String roomName = Integer.toString(random.nextInt(9999));
|
String roomCode = Integer.toString(random.nextInt(9999));
|
||||||
|
while (roomCode.length() < 4)
|
||||||
|
roomCode += 0 + roomCode;
|
||||||
|
|
||||||
while (roomName.length() < 4) roomName += 0 + roomName;
|
VRoom room = new VRoom();
|
||||||
|
|
||||||
rooms.put(roomName, userName);
|
room.setAdmin(user);
|
||||||
sendMessage(session, "createRoom", roomName);
|
room.setCode(roomCode);
|
||||||
|
room.setPublic(false);
|
||||||
|
room.setUsers(new ArrayList<VUser>());
|
||||||
|
|
||||||
System.err.println("[ROOM] Created room "+roomName+" by "+userName);
|
server.rooms.add(room);
|
||||||
|
user.setRoom(room);
|
||||||
|
|
||||||
|
SocketUtils.sendMessage(session, "roomCreate", roomCode);
|
||||||
|
|
||||||
|
System.err.println("[ROOM] Created room " + roomCode + " by " + user.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void connectRoom(WebSocketSession session, String roomName) throws InterruptedException, IOException {
|
@Override
|
||||||
if (roomName == null) {
|
public void connectRoom(WebSocketSession session, String roomCode) throws InterruptedException, IOException {
|
||||||
sendMessage(session, "error", "bad command command");
|
if (roomCode == null) {
|
||||||
return;
|
SocketUtils.sendMessage(session, "error", "bad command command");
|
||||||
}
|
|
||||||
if (!users.containsValue(session)) {
|
|
||||||
sendMessage(session, "error", "need login");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!rooms.containsKey(roomName)) {
|
|
||||||
sendMessage(session, "error", "no room");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String roomAdmin = rooms.get(roomName);
|
Optional<VRoom> oroom = server.rooms.stream().filter(room -> room.getCode().equals(roomCode)).findFirst();
|
||||||
sendMessage(session, "connectRoom", roomAdmin);
|
if (oroom.isEmpty()) {
|
||||||
|
SocketUtils.sendMessage(session, "error", "no room");
|
||||||
String userName = users.getKey(session);
|
return;
|
||||||
System.err.println("[ROOM] Connection to room "+roomName+" ("+roomAdmin+") by "+userName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void followRTC(WebSocketSession session, TextMessage message) throws InterruptedException, IOException {
|
VRoom room = oroom.get();
|
||||||
|
VUser user = UserUtils.getUser(session, server.users);
|
||||||
|
if (user == null) return;
|
||||||
|
|
||||||
|
user.setRoom(room);
|
||||||
|
room.getUsers().add(user);
|
||||||
|
|
||||||
|
ArrayList<String> usersName = new ArrayList<String>();
|
||||||
|
room.getUsers().forEach(userL -> usersName.add(userL.getName()));
|
||||||
|
|
||||||
|
JSONObject roomStatus = room.getRoomStatus();
|
||||||
|
roomStatus.put("users", usersName);
|
||||||
|
room.setRoomStatus(roomStatus);
|
||||||
|
|
||||||
|
SocketUtils.sendMessage(session, "roomConnect", "true");
|
||||||
|
SocketUtils.forwardMessage(session, "roomSettings", room.getRoomSettings());
|
||||||
|
SocketUtils.broadcast(room, "roomStatus", room.getRoomStatus());
|
||||||
|
SocketUtils.forwardMessage(room.getAdmin().getSession(), "roomStatus", room.getRoomStatus());
|
||||||
|
|
||||||
|
System.err
|
||||||
|
.println("[ROOM] Connection to room " + roomCode + " (" + room.getAdmin().getName() + ") by " + user.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void leaveRoom(WebSocketSession session) throws InterruptedException, IOException {
|
||||||
|
VUser user = UserUtils.getUser(session, server.users);
|
||||||
|
if (user == null) return;
|
||||||
|
|
||||||
|
VRoom room = UserUtils.getRoom(session, user);
|
||||||
|
if (room == null) return;
|
||||||
|
|
||||||
|
room.getUsers().remove(user);
|
||||||
|
user.setRoom(null);
|
||||||
|
|
||||||
|
ArrayList<String> usersName = new ArrayList<String>();
|
||||||
|
room.getUsers().forEach(userL -> usersName.add(userL.getName()));
|
||||||
|
|
||||||
|
JSONObject roomStatus = room.getRoomStatus();
|
||||||
|
roomStatus.put("users", usersName);
|
||||||
|
room.setRoomStatus(roomStatus);
|
||||||
|
|
||||||
|
SocketUtils.broadcast(room, "roomStatus", room.getRoomStatus());
|
||||||
|
SocketUtils.forwardMessage(room.getAdmin().getSession(), "roomStatus", room.getRoomStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRoomStatus(WebSocketSession session, TextMessage message) throws InterruptedException, IOException {
|
||||||
|
VUser user = UserUtils.getUser(session, server.users);
|
||||||
|
if (user == null) return;
|
||||||
|
|
||||||
|
VRoom room = UserUtils.getRoom(session, user);
|
||||||
|
if (room == null) return;
|
||||||
|
|
||||||
|
if (!UserUtils.checkIsAdmin(session, user)) return;
|
||||||
|
|
||||||
String payload = message.getPayload();
|
String payload = message.getPayload();
|
||||||
JSONObject jsonObject = new JSONObject(payload);
|
JSONObject jsonObject = new JSONObject(payload);
|
||||||
String target = (String) jsonObject.get("target");
|
|
||||||
|
|
||||||
if (target == null) {
|
ArrayList<String> usersName = new ArrayList<String>();
|
||||||
sendMessage(session, "error", "no target");
|
room.getUsers().forEach(userL -> usersName.add(userL.getName()));
|
||||||
|
|
||||||
|
JSONObject roomStatus = (JSONObject) jsonObject.get("message");
|
||||||
|
roomStatus.put("users", usersName);
|
||||||
|
|
||||||
|
room.setRoomStatus(roomStatus);
|
||||||
|
SocketUtils.broadcast(room, "roomStatus", room.getRoomStatus());
|
||||||
|
SocketUtils.forwardMessage(session, "roomStatus", room.getRoomStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRoomSettings(WebSocketSession session, TextMessage message) throws InterruptedException, IOException {
|
||||||
|
VUser user = UserUtils.getUser(session, server.users);
|
||||||
|
if (user == null) return;
|
||||||
|
|
||||||
|
VRoom room = UserUtils.getRoom(session, user);
|
||||||
|
if (room == null) return;
|
||||||
|
|
||||||
|
if (!UserUtils.checkIsAdmin(session, user)) return;
|
||||||
|
|
||||||
|
String payload = message.getPayload();
|
||||||
|
JSONObject jsonObject = new JSONObject(payload);
|
||||||
|
|
||||||
|
room.setRoomSettings((JSONObject) jsonObject.get("message"));
|
||||||
|
SocketUtils.broadcast(room, "roomSettings", room.getRoomSettings());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void forwardRoomMessage(WebSocketSession session, TextMessage message) throws InterruptedException, IOException {
|
||||||
|
VUser user = UserUtils.getUser(session, server.users);
|
||||||
|
if (user == null) return;
|
||||||
|
|
||||||
|
VRoom room = UserUtils.getRoom(session, user);
|
||||||
|
if (room == null) return;
|
||||||
|
|
||||||
|
String payload = message.getPayload();
|
||||||
|
JSONObject jsonObject = new JSONObject(payload);
|
||||||
|
String type = (String) jsonObject.get("type");
|
||||||
|
JSONObject data = (JSONObject) jsonObject.get("message");
|
||||||
|
|
||||||
|
if (room.getAdmin().equals(user)) SocketUtils.broadcast(room, type, data);
|
||||||
|
else SocketUtils.forwardMessage(room.getAdmin().getSession(), type, data);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void kickUser(WebSocketSession session, String username) throws InterruptedException, IOException {
|
||||||
|
VUser user = UserUtils.getUser(session, server.users);
|
||||||
|
if (user == null) return;
|
||||||
|
|
||||||
|
VRoom room = UserUtils.getRoom(session, user);
|
||||||
|
if (room == null) return;
|
||||||
|
|
||||||
|
if (!UserUtils.checkIsAdmin(session, user)) return;
|
||||||
|
|
||||||
|
if (username.isEmpty()) {
|
||||||
|
SocketUtils.sendMessage(session, "error", "bad command command");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocketSession targetSession = users.get(target);
|
Optional<VUser> ouserToKick = room.getUsers().stream().filter(userL -> userL.getName().equals(username)).findFirst();
|
||||||
|
|
||||||
if (targetSession == null) {
|
if (!ouserToKick.isPresent()) return;
|
||||||
sendMessage(session, "error", "unknow target");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
System.err.println("[ROOM] Foward RTC message");
|
VUser userToKick = ouserToKick.get();
|
||||||
followMessage(targetSession, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void followMessage (WebSocketSession session, TextMessage message) throws IOException {
|
room.getUsers().remove(userToKick);
|
||||||
session.sendMessage(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendMessage(WebSocketSession session, String type, String message) throws InterruptedException, IOException {
|
SocketUtils.sendMessage(userToKick.getSession(), "roomUserKick", "true");
|
||||||
session.sendMessage(new TextMessage("{\"type\":\""+type+"\",\"message\":\""+message+"\"}"));
|
|
||||||
|
SocketUtils.broadcast(room, "roomStatus", room.getRoomStatus());
|
||||||
|
SocketUtils.forwardMessage(session, "roomStatus", room.getRoomStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendServerInfos(WebSocketSession session) throws InterruptedException, IOException {
|
public void sendServerInfos(WebSocketSession session) throws InterruptedException, IOException {
|
||||||
session.sendMessage(new TextMessage("{\"type\":\"serverInfos\",\"userCount\":\""+users.size()+"\",\"roomCount\":\""+rooms.size()+"\"}"));
|
ServerStatus status = new ServerStatus();
|
||||||
}
|
status.setRoomCount(server.rooms.size());
|
||||||
|
status.setUserCount(server.users.size());
|
||||||
|
status.setRooms(new ArrayList<String>());
|
||||||
|
|
||||||
|
server.rooms.forEach(room -> {
|
||||||
|
if(room.isPublic()) status.getRooms().add(room.getCode());
|
||||||
|
});
|
||||||
|
|
||||||
|
SocketUtils.forwardMessage(session, "serverInfos", new JSONObject(status));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package gltronic.voozik.business;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.springframework.web.socket.TextMessage;
|
||||||
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
|
|
||||||
|
import gltronic.voozik.model.VRoom;
|
||||||
|
|
||||||
|
public class SocketUtils {
|
||||||
|
public static void sendMessage(WebSocketSession session, String type, String message) {
|
||||||
|
try {
|
||||||
|
session.sendMessage(new TextMessage("{\"type\":\"" + type + "\",\"message\": \"" + message.toString() + "\" }"));
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void forwardMessage(WebSocketSession session, String type, JSONObject message) {
|
||||||
|
try {
|
||||||
|
session.sendMessage(new TextMessage("{\"type\":\"" + type + "\",\"message\": " + message.toString() + " }"));
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void broadcast(VRoom room, String type, JSONObject message) {
|
||||||
|
room.getUsers().forEach(user -> {
|
||||||
|
forwardMessage(user.getSession(), type, message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
38
server/src/main/java/gltronic/voozik/business/UserUtils.java
Normal file
38
server/src/main/java/gltronic/voozik/business/UserUtils.java
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package gltronic.voozik.business;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
|
|
||||||
|
import gltronic.voozik.model.VRoom;
|
||||||
|
import gltronic.voozik.model.VUser;
|
||||||
|
|
||||||
|
public class UserUtils {
|
||||||
|
public static VUser getUser (WebSocketSession session, ArrayList<VUser> users) {
|
||||||
|
Optional<VUser> ouser = users.stream().filter(user -> user.getSession().equals(session)).findFirst();
|
||||||
|
|
||||||
|
if (ouser.isEmpty()) {
|
||||||
|
SocketUtils.sendMessage(session, "error", "need login");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ouser.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static VRoom getRoom (WebSocketSession session, VUser user) {
|
||||||
|
if (user.getRoom() == null) {
|
||||||
|
SocketUtils.sendMessage(session, "error", "no room");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else return user.getRoom();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean checkIsAdmin (WebSocketSession session, VUser user) {
|
||||||
|
if (!user.getRoom().getAdmin().equals(user)) {
|
||||||
|
SocketUtils.sendMessage(session, "error", "not admin");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
11
server/src/main/java/gltronic/voozik/model/Server.java
Normal file
11
server/src/main/java/gltronic/voozik/model/Server.java
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package gltronic.voozik.model;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class Server {
|
||||||
|
public ArrayList<VUser> users = new ArrayList<VUser>();
|
||||||
|
public ArrayList<VRoom> rooms = new ArrayList<VRoom>();
|
||||||
|
}
|
||||||
18
server/src/main/java/gltronic/voozik/model/ServerStatus.java
Normal file
18
server/src/main/java/gltronic/voozik/model/ServerStatus.java
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package gltronic.voozik.model;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class ServerStatus implements Serializable{
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
int userCount;
|
||||||
|
int roomCount;
|
||||||
|
List<String> rooms;
|
||||||
|
}
|
||||||
22
server/src/main/java/gltronic/voozik/model/VRoom.java
Normal file
22
server/src/main/java/gltronic/voozik/model/VRoom.java
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package gltronic.voozik.model;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class VRoom {
|
||||||
|
private String code;
|
||||||
|
private String name;
|
||||||
|
private boolean isPublic;
|
||||||
|
private JSONObject roomStatus;
|
||||||
|
private JSONObject roomSettings;
|
||||||
|
private VUser admin;
|
||||||
|
private List<VUser> users;
|
||||||
|
}
|
||||||
16
server/src/main/java/gltronic/voozik/model/VUser.java
Normal file
16
server/src/main/java/gltronic/voozik/model/VUser.java
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package gltronic.voozik.model;
|
||||||
|
|
||||||
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class VUser {
|
||||||
|
private String name;
|
||||||
|
private WebSocketSession session;
|
||||||
|
private VRoom room;
|
||||||
|
}
|
||||||
@@ -13,16 +13,18 @@ import org.springframework.web.socket.WebSocketSession;
|
|||||||
import org.springframework.web.socket.handler.TextWebSocketHandler;
|
import org.springframework.web.socket.handler.TextWebSocketHandler;
|
||||||
|
|
||||||
import gltronic.voozik.business.IRoomManager;
|
import gltronic.voozik.business.IRoomManager;
|
||||||
|
import gltronic.voozik.business.SocketUtils;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class SocketHandler extends TextWebSocketHandler {
|
public class SocketHandler extends TextWebSocketHandler {
|
||||||
List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();
|
private volatile List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
IRoomManager roomManager;
|
IRoomManager roomManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleTextMessage(WebSocketSession session, TextMessage message) throws InterruptedException, IOException {
|
public void handleTextMessage(WebSocketSession session, TextMessage message)
|
||||||
|
throws InterruptedException, IOException {
|
||||||
System.err.println("[WS] message :" + message.getPayload());
|
System.err.println("[WS] message :" + message.getPayload());
|
||||||
|
|
||||||
String payload = message.getPayload();
|
String payload = message.getPayload();
|
||||||
@@ -36,35 +38,54 @@ public class SocketHandler extends TextWebSocketHandler {
|
|||||||
case "login":
|
case "login":
|
||||||
roomManager.login(session, (String) jsonObject.get("name"));
|
roomManager.login(session, (String) jsonObject.get("name"));
|
||||||
break;
|
break;
|
||||||
case "createRoom":
|
|
||||||
roomManager.createRoom(session);
|
|
||||||
break;
|
|
||||||
case "connectRoom":
|
|
||||||
roomManager.connectRoom(session, (String) jsonObject.get("name"));
|
|
||||||
break;
|
|
||||||
case "leave":
|
case "leave":
|
||||||
roomManager.leave(session);
|
roomManager.leave(session);
|
||||||
break;
|
break;
|
||||||
case "offer":
|
case "roomCreate":
|
||||||
case "answer":
|
roomManager.createRoom(session);
|
||||||
case "candidate":
|
break;
|
||||||
roomManager.followRTC(session, message);
|
case "roomConnect":
|
||||||
|
roomManager.connectRoom(session, (String) jsonObject.get("code"));
|
||||||
|
break;
|
||||||
|
case "roomLeave":
|
||||||
|
roomManager.leaveRoom(session);
|
||||||
|
break;
|
||||||
|
case "roomStatus":
|
||||||
|
roomManager.setRoomStatus(session, message);
|
||||||
|
break;
|
||||||
|
case "roomSettings":
|
||||||
|
roomManager.setRoomSettings(session, message);
|
||||||
|
break;
|
||||||
|
case "roomUserKick":
|
||||||
|
roomManager.kickUser(session, (String) jsonObject.get("name"));
|
||||||
|
break;
|
||||||
|
case "roomVote":
|
||||||
|
case "roomUserCommand":
|
||||||
|
roomManager.forwardRoomMessage(session, message);
|
||||||
|
break;
|
||||||
|
case "search":
|
||||||
|
break;
|
||||||
|
case "alive":
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
roomManager.sendMessage(session, "error", "unknow command");
|
SocketUtils.sendMessage(session, "error", "unknow command");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
|
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
|
||||||
System.err.println("[WS] new connection");
|
System.err.println("[WS] new connection " + this);
|
||||||
sessions.add(session);
|
sessions.add(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus){
|
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) {
|
||||||
System.err.println("[WS] connection closed");
|
System.err.println("[WS] connection closed");
|
||||||
sessions.remove(session);
|
sessions.remove(session);
|
||||||
|
try {
|
||||||
roomManager.leave(session);
|
roomManager.leave(session);
|
||||||
|
} catch (InterruptedException | IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BIN
voozik.jar
BIN
voozik.jar
Binary file not shown.
Reference in New Issue
Block a user