-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
111 lines (103 loc) · 3.28 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/* eslint-disable max-classes-per-file */
const http = require('http')
const fs = require('fs')
const express = require('express')
const socketIo = require('socket.io')
const { allNotes, songToNotes } = require('./note')
const { midiToNote } = require('./static/js/piano-keys')
const { theKing } = require('./theKingCalledMeToPatrolMountains')
const app = express()
app.use('/static', express.static(`${__dirname}/static`))
const server = http.createServer(app)
const io = socketIo(server)
const { PORT = 80 } = process.env
server.listen(PORT, () => { console.log('listen on ', PORT) })
app.get('/', (req, res) => fs.createReadStream('./views/index.html').pipe(res))
let isPlaying = false
const hintQueue = []
const sleep = (t) => new Promise((rs) => setTimeout(rs, t))
const playTrack = async (socket, track) => {
const start = Date.now()
let expectedElapse = 0 // 用以补偿程序执行中消耗的时间
for (let i = 0; i < track.length; i += 1) {
const { n, d } = track[i]
// 只要有 d 的都传给前端,具体 hint 由前端处理
if (d) {
const randomNum = Math.random()
const hintObj = { ...track[i], id: randomNum }
// 观众也接收歌词
io.emit('hint', hintObj)
hintQueue.push(hintObj)
// 在 hint 的 4 秒内若按下了这个键,则更改 flag,服务器发送 note_on 的指令
const timeoutId = setTimeout(() => {
// 处理歌词
if (!n) {
clearTimeout(timeoutId)
return
}
const hintIndex = hintQueue.findIndex(({ id, flag }) => id === randomNum && flag)
// 后端打印 miss
if (hintIndex === -1) {
const note = midiToNote[n]
console.log(`Missed note: ${note}`)
clearTimeout(timeoutId)
return
}
const { name } = hintQueue[hintIndex]
// 演奏时如果不用 broadcast 的话,观众就听不到
socket.broadcast.emit('note_on', n, name)
hintQueue.splice(hintIndex, 1)
clearTimeout(timeoutId)
}, 4000)
}
expectedElapse += d
const totalElapse = Date.now() - start
const sleepTime = expectedElapse - totalElapse
// eslint-disable-next-line no-await-in-loop
await sleep(sleepTime)
}
}
io.on('connection', (socket) => {
let playerName = '某个重连的人'
// 检测是否保持连接
const intervalId = setInterval(() => {
socket.emit('heartbeat')
}, 500)
socket.on('set_name', (name) => {
playerName = name
console.log(`${playerName} connected.`)
})
socket.on('disconnect', () => {
clearInterval(intervalId)
console.log(`${playerName} disconnected.`)
})
socket.on('start', async (music) => {
// 前端单选曲目,按 start 按钮触发
if (!music) {
return
}
allNotes.theKing = theKing
songToNotes['大王叫我来巡山'] = 'theKing'
if (isPlaying) {
// 简单的锁,防止误触
console.log('Music is playing!')
return
}
isPlaying = true
// await 保证了重连后依然能收到 hint
await Promise.all(allNotes[songToNotes[music]].map((track) => playTrack(socket, track)))
isPlaying = false
})
socket.on('tap_hint', ({ id, name }) => {
// 点击音符后不直接发音
for (let i = 0; i < hintQueue.length; i += 1) {
const hintObj = hintQueue[i]
if (hintObj.id === id) {
hintObj.flag = true
hintObj.name = name
break
}
}
})
socket.on('note_on', (note, name) => socket.broadcast.emit('note_on', note, name))
})