voeg totale isolatie toe (nog zonder tijdlijn sniksnik)

master
Florens Douwes 5 years ago
parent 18aa4fa369
commit d5ab84f414
  1. 38
      backend/data/timelines.js
  2. 20
      backend/src/Room.ts
  3. 19
      backend/src/Service.ts
  4. 41
      backend/src/User.ts
  5. 48
      backend/src/index.ts
  6. 4
      backend/src/timeline.ts
  7. 25
      frontend/config-overrides.js
  8. 10
      frontend/src/components/Centurion.tsx
  9. 7
      frontend/src/components/Feed.tsx
  10. 167
      frontend/src/components/Lobby.tsx
  11. 10
      frontend/src/components/NextShot.tsx
  12. 39
      frontend/src/components/Player.ts
  13. 11
      frontend/src/components/ShotsTaken.tsx
  14. 1
      frontend/src/css/index.sass
  15. 4
      frontend/src/css/lobby.sass
  16. 41
      frontend/src/lib/Connection.ts
  17. 21
      frontend/src/types/types.ts

@ -2,6 +2,7 @@ module.exports = {
'timelines': [
{
'name': 'Centurion',
'songFile': 'songs/centurion.m4a',
'feed': [
{
@ -2728,6 +2729,43 @@ module.exports = {
]
}
]
},
{
'name': 'Totale isolatie',
'songFile': 'songs/totale_isolatie.m4a',
'feed': [
{
'timestamp': 0,
'events': [
{
'type': 'talk',
'text': [
'Ik heb geen tekst',
'want het is veel werk'
]
}
]
},
{
'timestamp': 10,
'events': [
{
'type': 'talk',
'text': [
'Nee echt',
'niks'
]
},
{
'type': 'song',
'text': [
'Blahblah',
'blahblahblah'
]
}
]
}
]
}
]
}

@ -1,9 +1,14 @@
import {Socket} from "socket.io";
import User from "./User";
import {getIndex, getNextShot, getTimeline, indexForTime} from "./timeline";
import {getIndex, getNextShot, getTimeline, getTimelineNames, indexForTime} from "./timeline";
import {getCurrentTime} from "./util";
export interface RoomOptions {
seekTime: number
timelineName: string
}
export default class Room {
id: number = 0;
users: User[] = [];
@ -14,6 +19,7 @@ export default class Room {
currentSeconds = 0;
timelineIndex: number = 0;
seekTime: number = 0;
timelineName: string = 'Centurion';
// For debugging purposes
@ -31,6 +37,8 @@ export default class Room {
'running': this.running,
'startTime': this.startTime,
'timelineName': this.timelineName,
'seekTime': this.seekTime,
'readyToParticipate': user.readyToParticipate || this.leader == user,
}
}
@ -67,9 +75,17 @@ export default class Room {
onBeforeDelete() {
}
setOptions(options: any) {
this.seekTime = Math.max(0, Math.min(options.seekTime, 250 * 60 * 1000))
if (getTimelineNames().indexOf(options.timelineName) >= 0) {
this.timelineName = options.timelineName;
}
this.sync()
}
start() {
this.running = true;
this.startTime = getCurrentTime() - 1400 * 1000
this.startTime = getCurrentTime() - this.seekTime
this.sync();
}

@ -1,7 +1,7 @@
import {Socket} from "socket.io";
import User from './User'
import Room from './Room'
import Room, {RoomOptions} from './Room'
import {getCurrentTime, randomInt} from "./util";
export default class Service {
@ -11,6 +11,7 @@ export default class Service {
onSocketConnect(socket: Socket) {
let user = new User(socket);
this.socketsToUsers.set(socket.id, user);
user.sync();
}
onSocketDisconnect(socket: Socket) {
@ -36,6 +37,14 @@ export default class Service {
})
}
onSetRoomOptions(socket: Socket, options: RoomOptions) {
let user = this.getUser(socket);
if (user.room?.getLeader() == user) {
user.room!!.setOptions(options)
}
}
onRequestStart(socket: Socket) {
let user = this.getUser(socket);
@ -56,9 +65,17 @@ export default class Service {
let room = this.roomIdToRooms.get(roomId)!!;
room.join(user);
return true;
}
onRequestReady(socket: Socket) {
let user = this.getUser(socket);
if (!user.room || user.readyToParticipate) return;
user.readyToParticipate = true;
user.sync();
}
onRequestJoinRandom(socket: Socket) {
let user = this.getUser(socket);

@ -1,12 +1,14 @@
import {Socket} from "socket.io";
import Room from "./Room";
import {getTimelineNames} from "./timeline";
export default class User {
socket: Socket;
id: string;
room: Room | null = null;
readyToParticipate: boolean = false;
constructor(socket: Socket) {
this.socket = socket;
@ -24,6 +26,7 @@ export default class User {
if (this.room != null) {
this.socket.leave(this.room.id.toString());
this.readyToParticipate = false;
}
this.room = room;
@ -35,18 +38,36 @@ export default class User {
this.sync();
}
getConfig() {
return {
'availableTimelines': getTimelineNames()
}
}
sentConfig: any = null;
sentRoom: any = null;
sentTimelineName: string | null = null;
sync() {
if (!this.shallowEquals(this.sentRoom, this.room?.serialize(this))) {
// Config
let config = this.getConfig();
if (!this.syncEquals(this.sentConfig, config)) {
this.sentConfig = config;
this.emit('config', {
'config': this.sentConfig
});
}
// Room
if (!this.syncEquals(this.sentRoom, this.room?.serialize(this))) {
this.sentRoom = this.room?.serialize(this);
this.emit('room', {
'room': this.sentRoom
})
}
if (!this.shallowEquals(this.sentTimelineName, this.room?.timelineName)) {
// Timeline
if (!this.syncEquals(this.sentTimelineName, this.room?.timelineName)) {
this.sentTimelineName = this.room?.timelineName || null;
this.emit('timeline', {
'timeline': this.sentTimelineName == null ? null : this.room!!.serializeTimeline(this)
@ -58,20 +79,30 @@ export default class User {
this.socket.emit(eventName, obj);
}
shallowEquals(obj1: any, obj2: any) {
syncEquals(obj1: any, obj2: any): boolean {
if (obj1 === undefined && obj2 === undefined)
return true;
if ((obj1 === undefined && obj2 !== undefined) || (obj1 !== undefined && obj2 === undefined))
return false;
if (obj1 === null && obj2 === null)
return true;
if ((obj1 === null && obj2 !== null) || (obj1 !== null && obj2 === null))
return false;
if (typeof (obj1) != typeof (obj2))
if (typeof (obj1) !== typeof (obj2))
return false;
if (typeof (obj1) === 'string' || typeof (obj1) === 'number' || typeof (obj1) === 'boolean') {
return obj1 === obj2;
}
if (Object.keys(obj1).length !== Object.keys(obj2).length) return false
return Object.keys(obj1).every(key =>
obj2.hasOwnProperty(key) && obj1[key] === obj2[key]
obj2.hasOwnProperty(key) && this.syncEquals(obj1[key], obj2[key])
);
}
}

@ -34,7 +34,15 @@ io.on('connection', socket => {
service.onTimeSync(socket, requestId, clientTime);
})
socket.on('request_start', () => {
socket.on('room_options', (options) => {
if (!options) return;
if (!options.timelineName || typeof (options.timelineName) !== 'string') return;
if (!Number.isSafeInteger(options.seekTime)) return;
service.onSetRoomOptions(socket, options);
});
socket.on('request_start', (options) => {
service.onRequestStart(socket);
});
@ -44,6 +52,10 @@ io.on('connection', socket => {
service.onRequestJoin(socket, roomId);
});
socket.on('request_ready', () => {
service.onRequestReady(socket);
})
socket.on('request_join_random', () => {
service.onRequestJoinRandom(socket);
})
@ -66,23 +78,23 @@ io.on('connection', socket => {
call.respond(service.hasRoomId(roomId));
return;
}
if (name == 'request_join') {
let roomId = params && params['roomId'];
if (!Number.isSafeInteger(roomId)) {
call.error('Invalid room id');
return;
}
if (!service.hasRoomId(roomId)) {
call.respond(false);
return;
}
if (service.onRequestJoin(socket, roomId)) {
call.respond(true);
} else {
call.respond(false);
}
}
//
// if (name == 'request_join') {
// let roomId = params && params['roomId'];
// if (!Number.isSafeInteger(roomId)) {
// call.error('Invalid room id');
// return;
// }
// if (!service.hasRoomId(roomId)) {
// call.respond(false);
// return;
// }
// if (service.onRequestJoin(socket, roomId)) {
// call.respond(true);
// } else {
// call.respond(false);
// }
// }
})
service.onSocketConnect(socket);

@ -2,6 +2,10 @@
import timeline from '../data/timelines.js';
export function getTimelineNames(): string[] {
return timeline.timelines.map((i: any) => i.name)
}
export function getTimeline(name: string) {
let t = timeline.timelines.find((i: any) => i.name == name);
if (!t) return null;

@ -1,4 +1,25 @@
module.exports = function override(config, env) {
// do stuff with the webpack config...
module.exports = {
webpack: function (config, env) {
return config;
},
devServer: function (configFunction) {
// Return the replacement function for create-react-app to use to generate the Webpack
// Development Server config. "configFunction" is the function that would normally have
// been used to generate the Webpack Development server config - you can use it to create
// a starting configuration to then modify instead of having to create a config from scratch.
return function (proxy, allowedHost) {
// Create the default config by calling configFunction with the proxy/allowedHost parameters
const config = configFunction(proxy, allowedHost);
config.proxy = {
"/socket.io": {
target: "http://localhost:3001",
ws: true
}
}
// Return your customised Webpack Development Server config.
return config;
};
}
}

@ -1,8 +1,7 @@
import React, {useRef, useState} from "react";
import React from "react";
import {Row} from "antd";
import {NumberParam, useQueryParam} from "use-query-params";
import {roomTime, useRoom, useRoomRunningChanged, useTick} from "../lib/Connection";
import {useRoomRunningAndReadyChanged} from "../lib/Connection";
import NextShot from "./NextShot";
import Feed from "./Feed";
import ShotsTaken from "./ShotsTaken";
@ -12,7 +11,8 @@ import logo from "../img/via-logo.svg";
import Player from "./Player";
const Centurion = () => {
let roomRunning = useRoomRunningChanged()?.running || false;
const room = useRoomRunningAndReadyChanged();
const showFeed = (room?.running && room.readyToParticipate) || false;
const feedContent = (
<React.Fragment>
@ -32,7 +32,7 @@ const Centurion = () => {
return (
<>
<section className="content">
{roomRunning ? feedContent : lobbyContent}
{showFeed ? feedContent : lobbyContent}
</section>
<footer>
<img src={logo} className="via-logo" alt="logo"/>

@ -1,23 +1,22 @@
import React, {useRef} from 'react';
import React from 'react';
import {Col} from "antd"
import {TimelineItem} from "../types/types";
import FeedItem from "./FeedItem"
import connection, {roomTime, useRoom, useRoomRunningChanged, useTimeline} from "../lib/Connection";
import {roomTime, useTimeline} from "../lib/Connection";
import {useUpdateAfterDelay} from "../util/hooks";
import CSSTransition from "react-transition-group/CSSTransition";
import TransitionGroup from "react-transition-group/TransitionGroup";
const Feed = (props: any) => {
const roomRunning = useRoomRunningChanged()?.running || false;
const timeline = useTimeline();
useUpdateAfterDelay(500)
let liveFeed: TimelineItem[] = [];
if (roomRunning && timeline != null) {
if (timeline != null) {
liveFeed = timeline.feed.filter(item => {
return item.timestamp * 1000 <= roomTime()
});

@ -1,33 +1,40 @@
import React, {MouseEvent, useState} from 'react';
import {Input, Button, Card, Col, InputNumber, Row, Space, Divider} from "antd"
import {Button, Card, Col, Divider, Form, Input, InputNumber, Row, Select} from "antd"
import {red} from '@ant-design/colors';
import connection, {useIsConnected, useRoom} from "../lib/Connection";
import connection, {useConfig, useIsConnected, useRoom} from "../lib/Connection";
import "../css/lobby.sass";
import beer from "../img/beer.png"
import {RoomOptions} from "../types/types";
const {Option} = Select;
const Lobby = (props: any) => {
const [seekTime, setSeekTime] = useState(0);
// Form/control states.
const [selectedRoomId, setSelectedRoomId] = useState(1);
const [seekTime, setSeekTime] = useState(0);
const [timelineName, setTimelineName] = useState(null);
const [joiningLobby, setJoiningLobby] = useState(false);
const [joinLobbyError, setJoinLobbyError] = useState(false);
// Room and logic states.
const isConnected = useIsConnected();
const room = useRoom();
const config = useConfig();
// @ts-ignore
const connectionType = connection.socket.io.engine.transport.name;
let isLeader = room?.isLeader || false;
let userCount = room?.userCount || 0;
function handleRequestStartClicked(e: MouseEvent) {
e.preventDefault();
connection.requestStart();
connection.requestStart(seekTime * 1000);
}
function handleJoin(e: MouseEvent) {
// connection.requestStart();
connection.requestReady();
}
function applyRoomId(v: number) {
@ -38,44 +45,79 @@ const Lobby = (props: any) => {
setJoiningLobby(true)
}
function joinRandomLobby() {
function handleJoinRandomLobby() {
connection.requestJoinRandom()
setJoinLobbyError(false);
}
// const {socket} = useSocket("welcome", async (obj: any) => {
// if (lobbyQueryId) {
// // lobbyId is already defined, this means we have a queryparam set.
// await onChangeLobbyInput(lobbyQueryId);
// return;
// }
// console.log("Got welcome", lobbyQueryId);
//
// setLobbyId(obj.room.name);
// setIsLeader(socket.id === obj.room.leaderId);
// setUserCount(obj.room.users?.length || 0);
// });
// const socketRef = useRef<SocketIOClient.Socket>(socket);
async function onChangeRoomInput(i: any) {
// setLobbyId(i);
// const result: any = await emit(connection.socket, 'join_room', i);
// setIsLeader(connection.socket.id === result.room.leaderId);
// setUserCount(result.room.users?.length || 0);
// connection.requestJoin(i);
function handleTimelineNameSet(timelineName: any) {
setTimelineName(timelineName);
connection.setRoomOptions(new RoomOptions(
seekTime || 0,
timelineName || room?.timelineName || ''))
}
function handleSetSeekTime(seekTime: number) {
setSeekTime(seekTime);
connection.setRoomOptions(new RoomOptions(
seekTime * 1000 || 0,
timelineName || room?.timelineName || ''))
}
// useEffect(() => {
// async function wrapper() {
// const result: any = await emit(connection.current, 'room_info');
// setIsLeader(connection.current.id === result.room.leaderId);
// setUserCount(result.room.users?.length || 0);
// }
//
// wrapper();
// }, []);
let leaderConfig = (
<Row justify="center">
<Col>
<Form
layout='horizontal'
labelCol={{span: 8}}
wrapperCol={{span: 24}}
>
<Form.Item label="Starttijd">
<Input
type="number"
suffix="sec"
value={seekTime}
onChange={v => handleSetSeekTime(parseInt(v.target.value) || 0)}/>
</Form.Item>
<Form.Item label="Nummer">
<Select defaultValue={(room && room.timelineName) || ''}
onChange={e => handleTimelineNameSet(e)}>
{config && config.availableTimelines.map((item, i) =>
<Option key={item} value={item}>{item}</Option>
)}
</Select>
</Form.Item>
</Form>
<Button
block
type="primary"
onClick={handleRequestStartClicked}>Start</Button>
</Col>
</Row>
)
let nonLeaderConfig = (
<Row justify="center">
<Col>
<p>
We gaan luisteren naar <b>{room && room.timelineName}</b> en
{room?.running && <span> zijn al gestart!</span>}
{!room?.running && <span> starten op {room?.seekTime} seconden</span>}
</p>
<Button
block
type="primary"
disabled={!room || room.readyToParticipate}
onClick={handleJoin}>{room && room.readyToParticipate ? 'Wachten op het startsein' : 'Kom erbij'}</Button>
</Col>
</Row>
)
// @ts-ignore
return (
<div className="lobby">
<Row>
@ -106,51 +148,28 @@ const Lobby = (props: any) => {
{isConnected &&
<Row justify="center">
<Col xs={12} sm={10} md={10} xl={6}>
<Col xs={24} sm={16} md={12} xl={10}>
<Card>
<h3>Huidige lobby: <b>{room?.id || 'Geen lobby'}</b></h3>
<Row>
<Col>
{/*<span>Verbonden met {connectionType}</span>*/}
{room &&
<span>
{userCount === 1 ?
<p>Er is één gebruiker aanwezig.</p>
:
<p>Er zijn {userCount} gebruikers aanwezig.</p>
}
<p>{isLeader ? 'Jij bent de baas van deze lobby.' : 'Wachten tot de baas de mix start.'}</p>
</Col>
</Row>
<Row>
<Col>
{isLeader &&
<span>Start de mix op
<Input
type="number"
min={0}
max={60000}
suffix="sec"
value={seekTime}
onChange={v => setSeekTime(parseInt(v.target.value) || 0)}/>
</span>
}
</Col>
</Row>
{isLeader ?
<Button
block
type="primary"
onClick={handleRequestStartClicked}>Start</Button>
:
<Button
block
type="primary"
disabled={!room}
onClick={handleJoin}>Join</Button>
{room &&
<span>Deel de link met je vrienden om mee te doen!</span>
}
<br/>
<br/>
{room && (isLeader ? leaderConfig : nonLeaderConfig)}
<Divider/>
@ -185,7 +204,7 @@ const Lobby = (props: any) => {
<Col>
<Button type="primary"
onClick={() => {
joinRandomLobby()
handleJoinRandomLobby()
}}>Join een willekeurige lobby</Button>
</Col>
</Row>

@ -1,14 +1,10 @@
import React, {useRef, useState} from 'react';
import React from 'react';
import {Col, Progress} from "antd"
import {useSocket} from "use-socketio/lib";
import {Tick} from "../types/types";
import connection, {roomTime, useRoom, useTimeline} from "../lib/Connection";
import {roomTime, useTimeline} from "../lib/Connection";
import {useUpdateAfterDelay} from "../util/hooks";
const NextShot = () => {
const room = useRoom()
const timeline = useTimeline()
useUpdateAfterDelay(1000)
@ -16,7 +12,7 @@ const NextShot = () => {
let remainingTime = 0;
let remainingPercentage = 0;
if (room?.running && timeline) {
if (timeline) {
const time = roomTime();
const [current, next] = timeline.itemAtTime(time, 'shot');

@ -1,20 +1,41 @@
import {roomTime, useRoom, useRoomRunningChanged, useRoomTime, useTick} from "../lib/Connection";
import {useRef} from "react";
import {roomTime, useRoomRunningAndReadyChanged, useRoomTime, useTimelineSongFileChanged} from "../lib/Connection";
import {useRef, useState} from "react";
const Player = () => {
const roomRunning = useRoomRunningChanged();
const roomRunning = useRoomRunningAndReadyChanged();
const _ = useRoomTime()
console.log('PLAYER RENDER', roomTime)
const timeline = useTimelineSongFileChanged();
const player = useRef(new Audio("centurion.m4a"));
const player = useRef(timeline ? new Audio(timeline.songFile) : null);
if (roomRunning?.running) {
const [timesSeeked, setTimesSeeked] = useState(0);
// If our time synchronisation algorithm thing thinks the time is off by more
// than this value, we seek the running player to correct it.
const diffSecondsRequiredToSeekRunningPlayer = 0.20;
// Hard cap we are allowed to seek this player. Some browsers are slow or inaccurate
// and will always be off. To avoid endless skipping of the song this cap stops seeking the
// player.
const maxTimesSeekAllow = 20;
if (player.current && roomRunning?.running && roomRunning.readyToParticipate) {
let targetTime = roomTime() / 1000;
let diff = player.current.currentTime - targetTime;
console.log('PLAYER DIFF', diff);
if (Math.abs(diff) > 0.1) {
console.log('PLAYER DIFF', 'Our time diff with the server: ', diff,
'Time required to seek (seconds): ', diffSecondsRequiredToSeekRunningPlayer);
if (Math.abs(diff) > diffSecondsRequiredToSeekRunningPlayer) {
if (timesSeeked < maxTimesSeekAllow) {
player.current.currentTime = targetTime;
setTimesSeeked(timesSeeked + 1);
console.log('SEEKED', 'The running player time was seeked, times seeked: ' + timesSeeked);
} else {
console.warn('The running player is off, but we\'ve changed the time ' +
'too often, skipping synchronizing the player.');
}
}
if (player.current.paused) {
@ -23,8 +44,10 @@ const Player = () => {
})
}
} else {
if (player.current) {
player.current.pause();
}
}
return null;
}

@ -1,22 +1,17 @@
import React, {useState} from 'react';
import React from 'react';
import {Col, Progress} from "antd"
import {useSocket} from "use-socketio/lib";
import {Tick} from "../types/types";
import {roomTime, useRoom, useTimeline} from "../lib/Connection";
import {roomTime, useTimeline} from "../lib/Connection";
import {useUpdateAfterDelay} from "../util/hooks";
const ShotsTaken = () => {
let room = useRoom();
let timeline = useTimeline();
useUpdateAfterDelay(1000);
let taken = 0;
if (room?.running && timeline) {
if (timeline) {
let [current, _] = timeline.eventAtTime(roomTime(), 'shot');
if (current) {
taken = current.shotCount!!;

@ -25,6 +25,7 @@ body
.via-logo
z-index: -10000
position: fixed
right: 0
bottom: 0

@ -2,7 +2,7 @@
.centurion-title
text-align: center
font-size: 3.0rem
font-size: 2.5rem
min-height: inherit
.text
@ -28,7 +28,7 @@
.hints
margin: 1rem 0 0 0
font-size: 2rem
font-size: 1.5rem
text-align: center
.control

@ -2,7 +2,7 @@ import io from "socket.io-client";
import {useEffect, useState} from "react";
import {parse as parseQueryString, stringify as stringifyQueryString} from 'query-string';
import {Room, Tick, Timeline} from "../types/types";
import {Config, Room, RoomOptions, Timeline} from "../types/types";
import {Sub, useSub} from "../util/sub";
class Connection {
@ -10,8 +10,8 @@ class Connection {
socket: SocketIOClient.Socket;
config = new Sub<Config | null>();
room = new Sub<Room | null>();
tick = new Sub<Tick | null>();
timeline = new Sub<Timeline | null>();
timeSyncIntervals = [500, 1000, 3000, 5000, 10000, 30000];
@ -47,13 +47,15 @@ class Connection {
this.onDisconnect();
})
this.socket.on('config', (data: any) => {
this.config.set(data.config);
})
this.socket.on('time_sync', (data: any) => {
this.timeSyncResponse(data.requestId, data.clientDiff, data.serverTime);
})
this.socket.on('room', (data: any) => {
console.log('ROOM', data.room);
if (data.room) {
this.setQueryLobbyId(data.room.id);
}
@ -61,10 +63,6 @@ class Connection {
this.room.set(data.room);
});
this.socket.on('tick_event', (data: any) => {
this.tick.set(data.tick);
});
this.socket.on('timeline', (data: any) => {
if (data.timeline) {
this.timeline.set(new Timeline(data.timeline));
@ -122,7 +120,6 @@ class Connection {
} else {
delete query.lobby;
}
console.log('QUERY', query);
let newUrl = window.location.protocol + "//" + window.location.host +
window.location.pathname + (Object.keys(query).length ? ('?' + stringifyQueryString(query)) : '');
window.history.pushState({}, '', newUrl);
@ -144,8 +141,14 @@ class Connection {
});
}
requestStart() {
this.socket.emit('request_start');
setRoomOptions(roomOptions: RoomOptions) {
this.socket.emit('room_options', roomOptions)
}
requestStart(seekTime: number) {
this.socket.emit('request_start', {
seekTime: seekTime
});
}
async requestJoin(roomId: number): Promise<boolean> {
@ -159,6 +162,10 @@ class Connection {
})
}
requestReady() {
this.socket.emit('request_ready');
}
requestJoinRandom() {
this.socket.emit('request_join_random');
}
@ -279,16 +286,20 @@ export function useRoom(): Room | null {
return useSub(connection.room);
}
export function useRoomRunningChanged(): Room | null {
return useSub(connection.room, (v) => [v && v.running]);
export function useConfig(): Config | null {
return useSub(connection.config);
}
export function useRoomRunningAndReadyChanged(): Room | null {
return useSub(connection.room, (v) => [v && v.running && v.readyToParticipate]);
}
export function useTimeline(): Timeline | null {
return useSub(connection.timeline);
}
export function useTick(): Tick | null {
return useSub(connection.tick);
export function useTimelineSongFileChanged(): Timeline | null {
return useSub(connection.timeline, (v) => [v && v.songFile]);
}
export function useRoomTime(): number {

@ -10,20 +10,39 @@ export interface Tick {
}
}
export interface Config {
availableTimelines: string[]
}
export interface Room {
id: number,
userCount: number,
isLeader: boolean,
running: boolean,
startTime: number
startTime: number,
seekTime: string,
timelineName: string,
readyToParticipate: boolean
}
export class RoomOptions {
seekTime: number
timelineName: string
constructor(seekTime: number, timelineName: string) {
this.seekTime = seekTime;
this.timelineName = timelineName;
}
}
export class Timeline {
name: string
songFile: string
feed: TimelineItem[]
constructor(obj: any) {
this.name = obj.name;
this.songFile = obj.songFile;
this.feed = obj.feed;
}

Loading…
Cancel
Save