Compare commits

...

10 Commits

Author SHA1 Message Date
Zsolt Zitting ccee2e2049 Firefox arc bug fix from esterTion, fix CORS errors for local HTML 2023-10-25 03:49:19 -07:00
Zsolt Zitting 449a25ba7b Add GitHub corner button 2023-10-25 03:47:19 -07:00
Zsolt Zitting 21549a0f04 Render at correct scale and height for cab when in fullscreen 2023-08-20 17:05:08 -07:00
Zsolt Zitting 845bef2080 adjust colors, attempt to fix offset calculation 2023-08-19 10:23:12 -07:00
WenqiOfficial 5b7d63bb3b Revert 2023-06-27 15:59:46 +08:00
WenqiOfficial 252df94d9e add decpage 2023-06-27 15:56:19 +08:00
WenqiOfficial ccfc70030f Update README.md 2023-06-14 17:46:35 +08:00
WenqiOfficial 9f30c58968 Update README.md 2023-06-11 01:45:26 +08:00
WenqiOfficial 90dda58d87 Update README.md 2023-06-11 01:39:14 +08:00
WenqiOfficial 7ca41fc85e
Create README.md 2023-06-11 01:25:33 +08:00
3 changed files with 117 additions and 53 deletions

40
README.md Normal file
View File

@ -0,0 +1,40 @@
# MercuryPlayerPlus
A map player of WACCA, plus for HACCA Project
Still updating...
## How to use?
You can deploy by yourself! ( •̀ ω •́ )
Or using my deployment(
Currently you need: **maps file** and **music file**.
I planed add song list in next version.(☆▽☆)
## Suggest?
You can use Github Issues, I will watch it. o(* ̄▽ ̄*)ブ
## Function
| | Function |
| ------- | ------- |
| √ | Map Playing |
|√ |Speed Changing|
|√|Note SE|
|Dev...|MV Playing|
|Dev...|Song List|
|||
|...|Waiting For Your Suggestions...|
## OnlineWeb
[VercelURL(Some areas might block it)](https://mercury-player-plus.vercel.app/)
[MyURL](https://player.hacca.wenqi.ml)

View File

@ -9,7 +9,7 @@
<style>.long-audio audio {width:1000px}</style>
<style>
.bgm,.bgm *{opacity:1 !important}
.bgm{display:flex;height:40px;line-height:40px;background:rgba(30,30,30,0.7);width:300px;text-align:center}
.bgm{display:flex;height:40px;line-height:40px;background:rgba(30,30,30,0.7);width:500px;text-align:center}
.bgm svg{fill:currentColor}
.bgm *:hover>svg{fill:#48a0f7}
.bgm *:hover:active>svg{fill:#2d89e6}
@ -24,9 +24,17 @@
.bgm .volume_slider input[type=range]{height:40px;margin:0;padding:0;width:100%}
.long-audio .bgm {width:1000px;max-width:calc(100vw - 16px)}
</style>
<style>
.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}
@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}
@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}
.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}
.github-corner {position:absolute; top:0; border:0; right:0; z-index:10}
</style>
</head>
<body ontouchstart>
<video src="https://cloud.zeroarea.ml:5221/api/v3/file/get/9419/mv_s01_016.mp4?sign=P90P_FYMwo7YAODlSee4oP1dMWir55SG6PQCnmPtZnI%3D%3A0" preload id="bga"></video>
<a href="https://github.com/yellowberryHN/MercuryPlayer" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:#fff; color:#151513" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>
<video src preload id="bga"></video>
<canvas id="canvas"></canvas>
<div style="position:fixed;left:5px;bottom:5px;color:white" class="no-hide">
<div>SE Volume: <select id="se_volume"></select> <input id="se_file" name="mer_se_file" type="file" accept="audio/*" placeholder="sound effect file path..."/></div>

118
main.js
View File

@ -62,6 +62,12 @@ const arrowCanvas = {
in: document.createElement('canvas'),
out: document.createElement('canvas'),
}
function pathToArcPoint(ctx, x, y, r, angle) {
ctx.lineTo(
x + Math.cos(angle) * r,
y + Math.sin(angle) * r,
)
}
function createArrows() {
arrowCanvas.in.width = maxR*2, arrowCanvas.in.height = maxR*2
arrowCanvas.out.width = maxR*2, arrowCanvas.out.height = maxR*2
@ -75,9 +81,9 @@ function createArrows() {
ctx.in.translate(maxR, maxR); ctx.in.rotate(Math.PI / 2); ctx.in.translate(-maxR, -maxR)
for (let i = 0; i < 30; i++) {
ctx.in.beginPath()
ctx.in.arc(maxR, maxR, maxR * 0.9, (i+0.25) * Math.PI / 15, (i+0.25) * Math.PI / 15)
ctx.in.arc(maxR, maxR, maxR * 0.8, (i+0.5) * Math.PI / 15, (i+0.5) * Math.PI / 15)
ctx.in.arc(maxR, maxR, maxR * 0.9, (i+0.75) * Math.PI / 15, (i+0.75) * Math.PI / 15)
pathToArcPoint(ctx.in, maxR, maxR, maxR * 0.9, (i+0.25) * Math.PI / 15)
pathToArcPoint(ctx.in, maxR, maxR, maxR * 0.8, (i+0.5) * Math.PI / 15)
pathToArcPoint(ctx.in, maxR, maxR, maxR * 0.9, (i+0.75) * Math.PI / 15)
ctx.in.strokeStyle = 'rgb(200,200,200)'; ctx.in.lineWidth = borderWidth; ctx.in.stroke()
ctx.in.strokeStyle = 'rgb(203,29,25)'; ctx.in.lineWidth = colorWidth; ctx.in.stroke()
}
@ -85,9 +91,9 @@ function createArrows() {
ctx.out.translate(maxR, maxR); ctx.out.rotate(Math.PI / 2); ctx.out.translate(-maxR, -maxR)
for (let i = 0; i < 30; i++) {
ctx.out.beginPath()
ctx.out.arc(maxR, maxR, maxR * 0.75, (i+0.25) * Math.PI / 15, (i+0.25) * Math.PI / 15)
ctx.out.arc(maxR, maxR, maxR * 0.85, (i+0.5) * Math.PI / 15, (i+0.5) * Math.PI / 15)
ctx.out.arc(maxR, maxR, maxR * 0.75, (i+0.75) * Math.PI / 15, (i+0.75) * Math.PI / 15)
pathToArcPoint(ctx.out, maxR, maxR, maxR * 0.75, (i+0.25) * Math.PI / 15)
pathToArcPoint(ctx.out, maxR, maxR, maxR * 0.85, (i+0.5) * Math.PI / 15)
pathToArcPoint(ctx.out, maxR, maxR, maxR * 0.75, (i+0.75) * Math.PI / 15)
ctx.out.strokeStyle = 'rgb(200,200,200)'; ctx.out.lineWidth = borderWidth; ctx.out.stroke()
ctx.out.strokeStyle = 'rgb(33,180,251)'; ctx.out.lineWidth = colorWidth; ctx.out.stroke()
}
@ -144,28 +150,43 @@ fetch('Table/music_info.json').then(r=>r.json()).then(r => {
console.error('failed loading music table', e)
})
const seContext = new AudioContext
//const bgmSrc = seContext.createMediaElementSource(bgm)
//bgmSrc.connect(seContext.destination)
let seBuffer = null
fetch('note.wav').then(r => r.arrayBuffer()).then(r => {
seContext.decodeAudioData(r, buf => {
if (buf) {
seBuffer = buf
} else {
console.error('decode failed')
}
}, e => console.error(e))
})
let seRBuffer = null
fetch('232.adx.wav').then(r => r.arrayBuffer()).then(r => {
seContext.decodeAudioData(r, buf => {
if (buf) {
seRBuffer = buf
} else {
console.error('decode failed')
}
}, e => console.error(e))
})
let clkBuffer = null
if (window.location.protocol !== "file:") {
fetch('note.wav').then(r => r.arrayBuffer()).then(r => {
seContext.decodeAudioData(r, buf => {
if (buf) {
seBuffer = buf
} else {
console.error('decode failed')
}
}, e => console.error(e))
})
fetch('rnote.wav').then(r => r.arrayBuffer()).then(r => {
seContext.decodeAudioData(r, buf => {
if (buf) {
seRBuffer = buf
} else {
console.error('decode failed')
}
}, e => console.error(e))
})
fetch('52.hca.wav').then(r => r.arrayBuffer()).then(r => {
seContext.decodeAudioData(r, buf => {
if (buf) {
clkBuffer = buf
} else {
console.error('decode failed')
}
}, e => console.error(e))
})
}
function loadUsingSelect() {
const id = music_select.value | 0
const diffi = diffi_select.value | 0
@ -584,7 +605,8 @@ function render(now) {
drawCount.actualFrame++
ctx.clearRect(0, 0, canvas.width, canvas.height)
const centerX = canvas.width / 2, centerY = canvas.height / 2
const cabView = (canvas.width == 1080 && canvas.height == 1920)
const centerX = canvas.width / 2, centerY = cabView ? (canvas.height / 2) - 58 : canvas.height / 2
// outer ring
ctx.lineWidth = 3
@ -593,10 +615,10 @@ function render(now) {
ctx.arc(centerX, centerY, maxR, 0, Math.PI * 2)
ctx.stroke()
if (enableBga) {
ctx.fillStyle = 'rgba(0,0,0,0.8)'
ctx.fillStyle = 'rgba(80,80,80,0.4)'
ctx.fill()
} else {
ctx.fillStyle = 'rgba(32,32,32,0.8)'
ctx.fillStyle = 'rgba(128,128,128,0.4)'
ctx.fill()
}
@ -628,7 +650,7 @@ function render(now) {
if (chartHeader.OFFSET) {
const offset = parseFloat(chartHeader.OFFSET)
if (!isNaN(offset)) {
startTs -= offset * 1000
startTs += offset * 1000
}
}
updateLaneOnState(-1, currentTs)
@ -657,9 +679,9 @@ function render(now) {
updateLaneOnState(previousTs, currentTs)
// black out "off" lanes
const laneBgGradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, maxR)
laneBgGradient.addColorStop(0, 'rgba(255,255,255,0)');
laneBgGradient.addColorStop(0.2, 'rgba(255,255,255,0.1)');
laneBgGradient.addColorStop(1, enableBga ? 'rgba(255,255,255,0.2)' : 'rgba(255,255,255,0.4)');
laneBgGradient.addColorStop(0, 'rgba(0,0,0,0.3)');
laneBgGradient.addColorStop(0.2, 'rgba(0,0,0,0.4)');
laneBgGradient.addColorStop(1, enableBga ? 'rgba(0,0,0,0.5)' : 'rgba(0,0,0,0.7)');
ctx.fillStyle = laneBgGradient
ctx.beginPath()
for (let i = 0; i < 60 * laneEffectMul; i++) {
@ -674,7 +696,7 @@ function render(now) {
ctx.lineTo(centerX, centerY)
}
ctx.fill()
const notesToRenderArr = getNotesForDraw(currentDistance - drawDistance * 0.1, drawDistance * 1.1)
const notesToRenderArr = getNotesForDraw(currentDistance - drawDistance * 0.1, drawDistance * 1.1).filter(i => i.timestamp > currentTs - 200)
notesToRender = {
sectionSep: [],
touch: [],
@ -693,7 +715,7 @@ function render(now) {
};
notesToRenderArr.forEach(i => {
switch (i.noteType) {
case 'sectionSep': {notesToRender.sectionSep.push(i); break;}
case 'sectionSep': {if (i.section) notesToRender.sectionSep.push(i); break;}
case '1': // touch
case '2': // bonus touch
case '20': {notesToRender.touch.push(i); break;} // touch R
@ -777,11 +799,7 @@ function render(now) {
Math.PI * (start / 30) + gapWidth, Math.PI * (end / 30) - gapWidth
)
} else {
ctx.arc(
centerX, centerY,
r,
Math.PI * (end / 30) - gapWidth, Math.PI * (end / 30) - gapWidth
)
pathToArcPoint(ctx, centerX, centerY, r, Math.PI * (end / 30) - gapWidth)
}
}
for (let i=nodeCount-1; i>=0; i--) {
@ -796,11 +814,7 @@ function render(now) {
true
)
} else {
ctx.arc(
centerX, centerY,
r,
Math.PI * (start / 30) + gapWidth, Math.PI * (start / 30) + gapWidth
)
pathToArcPoint(ctx, centerX, centerY, r, Math.PI * (start / 30) - gapWidth)
}
}
ctx.closePath()
@ -1007,7 +1021,7 @@ function render(now) {
ctx.lineWidth = i.w / 0.8 * 4 * displayRatio
ctx.beginPath()
i.n.forEach(i => {
ctx.arc(0, 0, maxR * (0.875 + 0.075 * i[0]), i[1], i[1])
pathToArcPoint(ctx, 0, 0, maxR * (0.875 + 0.075 * i[0]), i[1])
})
ctx.closePath()
ctx.fill()
@ -1037,6 +1051,7 @@ function render(now) {
ctx.stroke()
}
// circle mask
{
ctx.globalCompositeOperation = 'destination-in'
ctx.beginPath();
@ -1090,7 +1105,7 @@ function render(now) {
{
while (pendingSeTrigger.length && pendingSeTrigger[0] - currentTs < 100) {
if (!seBuffer) break
if (!seBuffer && !clkBuffer) break
if (pendingSeTrigger[0] - currentTs > -25) {
let bufSrc = seContext.createBufferSource()
bufSrc.buffer = seBuffer
@ -1103,7 +1118,7 @@ function render(now) {
if (!seRBuffer) break
if (pendingSeRTrigger[0] - currentTs > -25) {
let bufSrc = seContext.createBufferSource()
bufSrc.buffer = seRBuffer
bufSrc.buffer = clkBuffer != null ? clkBuffer : seRBuffer
bufSrc.connect(gain)
bufSrc.start(seContext.currentTime + Math.max(0, pendingSeRTrigger[0] - currentTs) / 1000)
}
@ -1122,7 +1137,7 @@ function render(now) {
}
}
let globalPlaybackOffset = 0
if (/win32/i.test(navigator.platform) && /firefox/i.test(navigator.userAgent)) globalPlaybackOffset = -60
//if (/win32/i.test(navigator.platform) && /firefox/i.test(navigator.userAgent)) globalPlaybackOffset = -60
let CALC_CONE_HEIGHT = 6
let CALC_CONE_RADIUS = 2
function distanceToRenderRadius (maxR, distance) {
@ -1283,11 +1298,12 @@ function resize() {
const w = Math.round(window.innerWidth * devicePixelRatio), h = Math.round(window.innerHeight * devicePixelRatio)
canvas.width = w
canvas.height = h
maxR = Math.round(Math.min(w, h) * 0.45)
const cabView = (canvas.width == 1080 && canvas.height == 1920)
maxR = cabView ? 530 : Math.round(Math.min(w, h) * 0.45)
drawForNextFrame = true
displayRatio = Math.max(w, h) / 1920
const wView = window.innerWidth, hView = window.innerHeight
const centerX = wView / 2, centerY = hView / 2
const centerX = wView / 2, centerY = cabView ? (hView / 2) - 58 : hView / 2
const rView = Math.round(Math.min(wView, hView) * 0.45)
if (enableBga) {