Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[renderer] show live song lyrics
Browse files Browse the repository at this point in the history
toyobayashi committed Nov 25, 2018
1 parent b154893 commit c1e984d
Showing 18 changed files with 426 additions and 325 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ before_install:
- yarn --verison
- cd app
- yarn global add node-gyp
- node-gyp install --target=3.0.9 --dist-url=https://atom.io/download/electron
- node-gyp install --target=3.0.10 --dist-url=https://atom.io/download/electron
install:
- yarn
script:
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -66,7 +66,7 @@
# if you have not downloaded Electron's C++ header
$ npm install -g node-gyp
$ node-gyp install --target=3.0.9 --dist-url=https://atom.io/download/electron
$ node-gyp install --target=3.0.10 --dist-url=https://atom.io/download/electron
# install dependencies
$ npm install
2 changes: 1 addition & 1 deletion README_CN.md
Original file line number Diff line number Diff line change
@@ -51,7 +51,7 @@
# 获取 Electron 用于编译原生模块的头文件
$ npm install -g node-gyp
$ node-gyp install --target=3.0.9 --dist-url=https://atom.io/download/electron
$ node-gyp install --target=3.0.10 --dist-url=https://atom.io/download/electron
# 安装依赖
$ npm install
2 changes: 1 addition & 1 deletion app/.npmrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
build_from_source=true
runtime=electron
target=3.0.9
target=3.0.10
dist_url=https://atom.io/download/electron
378 changes: 189 additions & 189 deletions app/package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mishiro",
"version": "1.5.2-dev",
"version": "1.6.0",
"description": "mishiro",
"main": "./public/mishiro.main.js",
"scripts": {
@@ -47,7 +47,7 @@
"cross-env": "^5.2.0",
"css-loader": "^1.0.1",
"cuint": "^0.2.2",
"electron": "3.0.9",
"electron": "3.0.10",
"electron-packager": "^12.2.0",
"express-serve-asar": "^1.0.1",
"file-loader": "^2.0.0",
@@ -78,6 +78,6 @@
"marked": "^0.5.1",
"mishiro-core": "^1.3.3",
"request": "^2.88.0",
"sqlite3": "^4.0.3"
"sqlite3": "^4.0.4"
}
}
3 changes: 2 additions & 1 deletion app/script/webpack.config.ts
Original file line number Diff line number Diff line change
@@ -117,7 +117,7 @@ export const renderer: webpack.Configuration = {
extensions: ['.ts', '.js', '.vue', '.css']
},
externals: [webpackNodeExternals({
whitelist: mode === 'production' ? [/vue/] : [/webpack/]
whitelist: mode === 'production' ? [/vue/] : [/webpack/, /vue-i18n/]
})],
plugins: [
new VueLoaderPlugin(),
@@ -167,6 +167,7 @@ if (mode === 'production') {
}
})
main.optimization = {
...(main.optimization || {}),
minimizer: [terser()]
}
renderer.plugins = [
3 changes: 2 additions & 1 deletion app/src/ts/i18n/en-US.ts
Original file line number Diff line number Diff line change
@@ -122,7 +122,8 @@ export default {
noAudio: 'MP3 file not found',
start: 'START',
gameRunning: 'Live is being held。',
liveResult: 'LIVE RESULT'
liveResult: 'LIVE RESULT',
noLyrics: 'NO LYRICS'
},
gacha: {
ikkai: 'ONCE',
3 changes: 2 additions & 1 deletion app/src/ts/i18n/ja-JP.ts
Original file line number Diff line number Diff line change
@@ -122,7 +122,8 @@ export default {
noAudio: 'MP3ファイルが見つかりませんでした。',
start: '決定',
gameRunning: 'ライブ進行中です。',
liveResult: 'ライブ成績'
liveResult: 'ライブ成績',
noLyrics: '歌詞なし'
},
gacha: {
ikkai: '1回引く',
3 changes: 2 additions & 1 deletion app/src/ts/i18n/zh-CN.ts
Original file line number Diff line number Diff line change
@@ -122,7 +122,8 @@ export default {
noAudio: '未発現MP3文件。',
start: '開始',
gameRunning: 'LIVE正在進行。',
liveResult: 'LIVE成績'
liveResult: 'LIVE成績',
noLyrics: '無歌詞'
},
gacha: {
ikkai: '単抽',
5 changes: 5 additions & 0 deletions app/src/ts/main/ipc.ts
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ import onMasterRead, { MasterData } from './on-master-read'
import onManifestQuery from './on-manifest-query'
import onManifestSearch from './on-manifest-search'
import onGame from './on-game'
import onLyrics from './on-lyrics'

export default function () {
let manifestData: any = {}
@@ -41,4 +42,8 @@ export default function () {
ipcMain.on('game', (event: Event, scoreFile: string, difficulty: string, bpm: number, audioFile: string) => {
onGame(event, scoreFile, difficulty, bpm, audioFile).catch(err => console.log(err))
})

ipcMain.on('lyrics', (event: Event, scoreFile: string) => {
onLyrics(event, scoreFile).catch(err => console.log(err))
})
}
26 changes: 26 additions & 0 deletions app/src/ts/main/on-lyrics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { openSqlite } from './sqlite3'
import * as path from 'path'
import { Event } from 'electron'

export default async function (event: Event, scoreFile: string) {
let bdb = await openSqlite(scoreFile)
let rows = await bdb._all('SELECT name, data FROM blobs')
bdb.close()
let name = path.parse(scoreFile).name.split('_')
let musicscores = name[0]
let mxxx = name[1]

let nameField = `${musicscores}/${mxxx}/${mxxx}_lyrics.csv`
let data = rows.filter((row: any) => row.name === nameField)[0].data.toString()
const list = data.split('\n')
const lyrics = []
for (let i = 1; i < list.length - 1; i++) {
const line = list[i].split(',')
lyrics.push({
time: Number(line[0]),
lyrics: line[1],
size: line[2]
})
}
event.sender.send('lyrics', lyrics)
}
23 changes: 0 additions & 23 deletions app/src/ts/renderer-game.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,7 @@
import '../css/game.css'
import { ipcRenderer, Event } from 'electron'

import { Game, newImage, keyBind } from './renderer/game'
import Vue from 'vue'
import MishiroGame from '../vue/MishiroGame.vue'

window.addEventListener('load', () => {
keyBind()
let canvasLive = document.getElementById('live') as HTMLCanvasElement

Game.CTX = canvasLive.getContext('2d') as CanvasRenderingContext2D
let canvasIconBar = document.getElementById('iconBar') as HTMLCanvasElement

Game.BACK_CTX = canvasIconBar.getContext('2d') as CanvasRenderingContext2D
let ctxIconBar = Game.BACK_CTX
let liveIcon = newImage('../../asset/img.asar/live_icon_857x114.png')
liveIcon.addEventListener('load', function () {
ctxIconBar.drawImage(this, 211.5, 586)
}, false)

}, false)

ipcRenderer.on('start', (_event: Event, song: any, fromWindowId: number) => {
Game.start(song, fromWindowId)
})

// tslint:disable-next-line:no-unused-expression
new Vue({
el: '#app',
13 changes: 13 additions & 0 deletions app/src/ts/renderer/game.ts
Original file line number Diff line number Diff line change
@@ -104,6 +104,19 @@ class Game {
public static H: number = 102
public static DISTANCE: number = Game.TOP_TO_BOTTOM + Game.H
public static RANGE: number = 100
public static init () {
keyBind()
let canvasLive = document.getElementById('live') as HTMLCanvasElement
Game.CTX = canvasLive.getContext('2d') as CanvasRenderingContext2D

let canvasIconBar = document.getElementById('iconBar') as HTMLCanvasElement
Game.BACK_CTX = canvasIconBar.getContext('2d') as CanvasRenderingContext2D

let liveIcon = newImage('../../asset/img.asar/live_icon_857x114.png')
liveIcon.addEventListener('load', function () {
Game.BACK_CTX.drawImage(this, 211.5, 586)
}, false)
}
public static start (song: any, fromWindowId: number) {
const prefix = 80
let isCompleted = false
232 changes: 142 additions & 90 deletions app/src/ts/renderer/mishiro-live.ts
Original file line number Diff line number Diff line change
@@ -42,6 +42,8 @@ export default class extends Vue {
allLive: boolean = true
liveQueryList: any[] = []
isGameRunning: boolean = false
allLyrics: { time: number; lyrics: string; size: any}[] = []
lyrics: { time: number; lyrics: string; size: any}[] = []

@Prop({ default: () => ({}) }) master: MasterData

@@ -56,106 +58,140 @@ export default class extends Vue {
this.bgm.currentTime = Number((this.$refs.playProg as HTMLInputElement).value)
}
async selectAudio (audio: any) {
if (this.activeAudio.hash !== audio.hash) {
this.playSe(this.enterSe)
if (this.activeAudio.hash === audio.hash) return

this.total = 0
this.current = 0
this.text = ''
this.playSe(this.enterSe)

if (audio.name.split('/')[0] === 'b') {
if (!fs.existsSync(bgmDir(audio.fileName))) {
if (navigator.onLine) {
this.dler.stop()
this.activeAudio = audio
let result: string | boolean = false
try {
// result = await this.dler.downloadOne(
// this.getBgmUrl(audio.hash),
// bgmDir(audio.name.split('/')[1]),
// (prog) => {
// this.text = prog.name as string
// this.current = prog.loading
// this.total = prog.loading
// }
// )
result = await this.dler.downloadSound(
'b',
audio.hash,
bgmDir(path.basename(audio.name)),
(prog) => {
this.text = prog.name as string
this.current = prog.loading
this.total = prog.loading
}
)
} catch (errorPath) {
this.event.$emit('alert', this.$t('home.errorTitle'), this.$t('home.downloadFailed') + '<br/>' + errorPath)
}
if (result) {
this.total = 99.99
this.current = 99.99
this.text += this.$t('live.decoding')
await this.acb2mp3(bgmDir(path.basename(audio.name)), audio.fileName)
this.total = 0
this.current = 0
this.text = ''
this.event.$emit('liveSelect', { src: `../../asset/bgm/${audio.fileName}` })
}
} else {
this.event.$emit('alert', this.$t('home.errorTitle'), this.$t('home.noNetwork'))
this.total = 0
this.current = 0
this.text = ''

if (audio.name.split('/')[0] === 'b') {
if (!fs.existsSync(bgmDir(audio.fileName))) {
if (navigator.onLine) {
this.dler.stop()
this.activeAudio = audio
let result: string | boolean = false
try {
// result = await this.dler.downloadOne(
// this.getBgmUrl(audio.hash),
// bgmDir(audio.name.split('/')[1]),
// (prog) => {
// this.text = prog.name as string
// this.current = prog.loading
// this.total = prog.loading
// }
// )
result = await this.dler.downloadSound(
'b',
audio.hash,
bgmDir(path.basename(audio.name)),
(prog) => {
this.text = prog.name as string
this.current = prog.loading
this.total = prog.loading
}
)
} catch (errorPath) {
this.event.$emit('alert', this.$t('home.errorTitle'), this.$t('home.downloadFailed') + '<br/>' + errorPath)
}
if (result) {
this.total = 99.99
this.current = 99.99
this.text += this.$t('live.decoding')
await this.acb2mp3(bgmDir(path.basename(audio.name)), audio.fileName)
this.total = 0
this.current = 0
this.text = ''
this.event.$emit('liveSelect', { src: `../../asset/bgm/${audio.fileName}` })
}
} else {
this.activeAudio = audio
this.event.$emit('liveSelect', { src: `../../asset/bgm/${audio.fileName}` })
this.event.$emit('alert', this.$t('home.errorTitle'), this.$t('home.noNetwork'))
}
} else if (audio.name.split('/')[0] === 'l') {
if (!fs.existsSync(liveDir(audio.fileName))) {
if (navigator.onLine) {
this.dler.stop()
this.activeAudio = audio
let result: string | boolean = false
try {
// result = await this.dler.downloadOne(
// this.getLiveUrl(audio.hash),
// liveDir(audio.name.split('/')[1]),
// (prog) => {
// this.text = prog.name as string
// this.current = prog.loading
// this.total = prog.loading
// }
// )
result = await this.dler.downloadSound(
'l',
audio.hash,
liveDir(path.basename(audio.name)),
(prog) => {
this.text = prog.name as string
this.current = prog.loading
this.total = prog.loading
}
)
} catch (errorPath) {
this.event.$emit('alert', this.$t('home.errorTitle'), this.$t('home.downloadFailed') + '<br/>' + errorPath)
}
if (result) {
this.total = 99.99
this.current = 99.99
this.text += this.$t('live.decoding')
await this.acb2mp3(liveDir(path.basename(audio.name)), audio.fileName)
this.total = 0
this.current = 0
this.text = ''
this.event.$emit('liveSelect', { src: `../../asset/live/${audio.fileName}` })
} else {
this.activeAudio = audio
this.event.$emit('liveSelect', { src: `../../asset/bgm/${audio.fileName}` })
}
} else if (audio.name.split('/')[0] === 'l') {
if (!fs.existsSync(liveDir(audio.fileName))) {
if (!navigator.onLine) {
this.event.$emit('alert', this.$t('home.errorTitle'), this.$t('home.noNetwork'))
return
}
this.dler.stop()
this.activeAudio = audio
let result: string | boolean = false
try {
// result = await this.dler.downloadOne(
// this.getLiveUrl(audio.hash),
// liveDir(audio.name.split('/')[1]),
// (prog) => {
// this.text = prog.name as string
// this.current = prog.loading
// this.total = prog.loading
// }
// )
result = await this.dler.downloadSound(
'l',
audio.hash,
liveDir(path.basename(audio.name)),
(prog) => {
this.text = prog.name as string
this.current = prog.loading
this.total = prog.loading
}
)
} catch (errorPath) {
this.event.$emit('alert', this.$t('home.errorTitle'), this.$t('home.downloadFailed') + '<br/>' + errorPath)
return
}

if (!result) return

this.total = 99.99
this.current = 99.99
this.text += this.$t('live.decoding')
await this.acb2mp3(liveDir(path.basename(audio.name)), audio.fileName)
this.total = 0
this.current = 0
this.text = ''
this.event.$emit('liveSelect', { src: `../../asset/live/${audio.fileName}` })

} else {
this.activeAudio = audio
this.event.$emit('liveSelect', { src: `../../asset/live/${audio.fileName}` })
}

this.lyrics = []
this.allLyrics = []

if (!this.activeAudio.score) return

if (!fs.existsSync(scoreDir(this.activeAudio.score))) {
if (!navigator.onLine) {
this.event.$emit('alert', this.$t('home.errorTitle'), this.$t('home.noNetwork'))
return
}
try {
let scoreBdb = await this.scoreDownloader.downloadDatabase(
this.activeAudio.scoreHash,
scoreDir(this.activeAudio.score.split('.')[0])
)
if (scoreBdb) {
// this.core.util.lz4dec(scoreBdb as string, 'bdb')
fs.removeSync(scoreDir(this.activeAudio.score.split('.')[0]))
} else {
this.event.$emit('alert', this.$t('home.errorTitle'), this.$t('home.noNetwork'))
this.event.$emit('alert', this.$t('home.errorTitle'), 'Error!')
return
}
} else {
this.activeAudio = audio
this.event.$emit('liveSelect', { src: `../../asset/live/${audio.fileName}` })
} catch (errorPath) {
this.event.$emit('alert', this.$t('home.errorTitle'), this.$t('home.downloadFailed') + '<br/>' + errorPath)
return
}
}

ipcRenderer.send('lyrics', scoreDir(this.activeAudio.score))

}
}
query () {
@@ -188,6 +224,9 @@ export default class extends Vue {
shell.showItemInFolder(dirl + '/.')
}
}
openLyrics () {
this.event.$emit('alert', path.parse(this.activeAudio.fileName).name, this.allLyrics.map(line => line.lyrics).join('<br/>'))
}
async startGame () {
this.playSe(this.enterSe)

@@ -236,11 +275,20 @@ export default class extends Vue {
this.$nextTick(() => {
this.bgm.addEventListener('timeupdate', () => {
this.currentTime = this.bgm.currentTime
for (let i = this.allLyrics.length - 1; i >= 0; i--) {
const line = this.allLyrics[i]
if (this.bgm.currentTime >= line.time) {
this.lyrics = i === this.allLyrics.length - 1 ? [this.allLyrics[i]] : [this.allLyrics[i], this.allLyrics[i + 1]]
break
}
}
}, false)
this.bgm.addEventListener('durationchange', () => {
this.duration = this.bgm.duration
}, false)
this.event.$on('playerSelect', (fileName: string) => {
this.allLyrics = []
this.lyrics = []
if (this.bgmManifest.filter(bgm => bgm.fileName === fileName).length > 0) {
this.activeAudio = this.bgmManifest.filter(bgm => bgm.fileName === fileName)[0]
} else {
@@ -260,6 +308,10 @@ export default class extends Vue {
if (isCompleted) this.playSe(new Audio('../../asset/se.asar/se_live_wow.mp3'))
this.event.$emit('showLiveResult', liveResult)
})
ipcRenderer.on('lyrics', (_event: Event, lyrics: { time: number; lyrics: string; size: any }[]) => {
console.log(lyrics)
this.allLyrics = lyrics
})
})
}
}
25 changes: 17 additions & 8 deletions app/src/vue/MishiroGame.vue
Original file line number Diff line number Diff line change
@@ -9,19 +9,28 @@
</div>
</template>

<script>
<script lang="ts">
import TheCombo from './component/TheCombo.vue'
import TheLiveGauge from './component/TheLiveGauge.vue'
import { liveResult } from '../ts/renderer/game'
export default {
import { liveResult, Game } from '../ts/renderer/game'
import { Vue, Component } from 'vue-property-decorator'
import { ipcRenderer, Event } from 'electron'
@Component({
components: {
TheCombo,
TheLiveGauge
},
data () {
return {
liveResult
}
}
})
export default class extends Vue {
liveResult = liveResult
mounted () {
this.$nextTick(() => {
ipcRenderer.on('start', (_event: Event, song: any, fromWindowId: number) => {
Game.start(song, fromWindowId)
})
Game.init()
})
}
}
</script>
21 changes: 18 additions & 3 deletions app/src/vue/view/MishiroLive.vue
Original file line number Diff line number Diff line change
@@ -25,9 +25,17 @@

<div class="margin-top-20 clearfix live-bottom">
<TaskLoading :total-loading="total" :current-loading="current" :text="text" :single="true" class="absolute-left" :color="'live'"/>
<div class="gray-bg absolute-right flex-center timebar">
<p>{{Math.floor(currentTime) | time}} / {{Math.floor(duration) | time}}</p>
<input type="range" ref="playProg" :max="duration" min="0" :value="currentTime" @input="oninput()" :style="{ 'background-size': 100 * (currentTime / duration) + '% 100%' }">
<div class="gray-bg absolute-right timebar">
<div style="width: 100%;">
<input type="range" ref="playProg" :max="duration" min="0" :value="currentTime" @input="oninput()" style="width: calc(100% - 130px);" :style="{ 'background-size': 100 * (currentTime / duration) + '% 100%' }">
<span style="display: inline-block; margin-left: 10px;">{{Math.floor(currentTime) | time}} / {{Math.floor(duration) | time}}</span>
</div>
<div class="lyrics" v-if="allLyrics.length">
<span @click="openLyrics" :style="{ color: lyrics.indexOf(l) === 0 ? '#902070' : '#000', 'font-size': lyrics.indexOf(l) === 0 ? '18px' : void 0 }" v-for="l in lyrics" :key="l.time">{{l.lyrics}}</span>
</div>
<div class="lyrics" style="justify-content: center;" v-else>
<span style="font-size: 25px;">{{$t('live.noLyrics')}}</span>
</div>
</div>
</div>
</div>
@@ -40,6 +48,13 @@
.timebar{
font-family: "CGSS-B";
}
.timebar .lyrics {
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
height: 65px;
}
.absolute-left{
position: absolute;
left: 0;
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ install:
yarn global add node-gyp
node-gyp install --target=3.0.9 --dist-url=https://atom.io/download/electron
node-gyp install --target=3.0.10 --dist-url=https://atom.io/download/electron
yarn
build_script:

0 comments on commit c1e984d

Please sign in to comment.