feat: send word-by-word and translated lyrics to clients
This commit is contained in:
1 parent
e2364b171e
commit
a38d6ca3e2
10 files changed
+71
-11
No files matched your search
@@ -126,9 +126,18 @@ func (s *server) nowPlaying(p *room.NowPlaying) *protocol.NowPlaying {
|
||||
DurationMs: p.Entry.Prep.Duration.Milliseconds(),
|
||||
Gain: p.Entry.Prep.Gain,
|
||||
Lyric: p.Entry.Prep.Lyric,
|
||||
LyricWords: optional(p.Entry.Prep.LyricWords),
|
||||
LyricTrans: optional(p.Entry.Prep.LyricTrans),
|
||||
}
|
||||
}
|
||||
|
||||
func optional(s string) *string {
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
return &s
|
||||
}
|
||||
|
||||
func (s *server) mediaURL(t track.Track) string {
|
||||
if media.Stored(t.Source) {
|
||||
return s.StorageURL + "/" + media.AudioObject(media.Key(t.ID))
|
||||
|
||||
@@ -71,6 +71,16 @@ func TestSnapshotDTO(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNowPlayingCarriesWordAndTranslatedLyrics(t *testing.T) {
|
||||
s := &server{}
|
||||
entry := sunnyEntry
|
||||
entry.Prep.LyricWords, entry.Prep.LyricTrans = "[1000,500](1000,500,0)故", "[00:01.00]The story"
|
||||
got := s.nowPlaying(&room.NowPlaying{Entry: entry})
|
||||
if got.LyricWords == nil || *got.LyricWords != entry.Prep.LyricWords || got.LyricTrans == nil || *got.LyricTrans != entry.Prep.LyricTrans {
|
||||
t.Errorf("lyricWords = %v, lyricTrans = %v", got.LyricWords, got.LyricTrans)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventDTO(t *testing.T) {
|
||||
s := &server{}
|
||||
sentAt := time.UnixMilli(1760000000456)
|
||||
|
||||
@@ -27,11 +27,13 @@ type Source struct {
|
||||
}
|
||||
|
||||
type sourceSong struct {
|
||||
id string
|
||||
meta track.Meta
|
||||
lyric string
|
||||
audio []byte
|
||||
reject string
|
||||
id string
|
||||
meta track.Meta
|
||||
lyric string
|
||||
lyricWords string
|
||||
lyricTrans string
|
||||
audio []byte
|
||||
reject string
|
||||
}
|
||||
|
||||
type sourcePlaylist struct {
|
||||
@@ -56,6 +58,16 @@ func (s *Source) Add(id string, m track.Meta, lyric string, audio []byte) {
|
||||
s.songs = append(s.songs, sourceSong{id: id, meta: m, lyric: lyric, audio: audio})
|
||||
}
|
||||
|
||||
func (s *Source) SetLyrics(id, words, trans string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
for i := range s.songs {
|
||||
if s.songs[i].id == id {
|
||||
s.songs[i].lyricWords, s.songs[i].lyricTrans = words, trans
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Source) Reject(id, reason string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
@@ -164,7 +176,7 @@ func (s *Source) Resolve(_ context.Context, id string) (port.Resolved, error) {
|
||||
case song.reject != "":
|
||||
return port.Resolved{}, &port.RejectedError{Err: errors.New(song.reject)}
|
||||
}
|
||||
return port.Resolved{Meta: song.meta, Lyric: song.lyric, Audio: io.NopCloser(bytes.NewReader(song.audio))}, nil
|
||||
return port.Resolved{Meta: song.meta, Lyric: song.lyric, LyricWords: song.lyricWords, LyricTrans: song.lyricTrans, Audio: io.NopCloser(bytes.NewReader(song.audio))}, nil
|
||||
}
|
||||
|
||||
func (s *Source) PlayURL(_ context.Context, id string) (string, error) {
|
||||
|
||||
@@ -69,6 +69,20 @@ func checkDecodeWarning(t *testing.T, logs *bytes.Buffer, tr track.Track, decode
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunSavesWordAndTranslatedLyrics(t *testing.T) {
|
||||
source := porttest.NewSource(track.NetEase)
|
||||
source.Add("186016", meta, "[00:01.00]故事", []byte("不是音频"))
|
||||
source.SetLyrics("186016", "[1000,500](1000,500,0)故", "[00:01.00]The story")
|
||||
tracks, err := run(t, source, sunny)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := track.Prepared{Duration: meta.Duration, Gain: 1, Lyric: "[00:01.00]故事", LyricWords: "[1000,500](1000,500,0)故", LyricTrans: "[00:01.00]The story"}
|
||||
if got, _ := tracks.row(sunny); got.Result != want {
|
||||
t.Errorf("准备结果 = %+v,期望 %+v", got.Result, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunFallsBackWhenAudioCannotBeAnalyzed(t *testing.T) {
|
||||
source := porttest.NewSource(track.NetEase)
|
||||
source.Add("186016", meta, "", []byte("不是音频"))
|
||||
|
||||
@@ -156,6 +156,7 @@ func contractSources() sources {
|
||||
"[00:01.00]故事的小黄花", porttest.SilentWAV(269500*time.Millisecond))
|
||||
netease.Add("185811", track.Meta{Title: "七里香", Artist: "周杰伦", Album: "七里香", CoverURL: "https://p1.music.126.net/qilixiang.jpg", PageURL: "https://music.163.com/#/song?id=185811", Duration: 299 * time.Second},
|
||||
"", porttest.SilentWAV(299*time.Second))
|
||||
netease.SetLyrics("185811", "[1000,500](1000,500,0)窗", "[00:01.00]Window")
|
||||
netease.Add("1100", track.Meta{Title: "片头", Artist: "无名", PageURL: "https://music.163.com/#/song?id=1100", Duration: 15 * time.Second},
|
||||
"", porttest.SilentWAV(15*time.Second))
|
||||
netease.Add("404", track.Meta{Title: "不能播放的歌", Artist: "无名", PageURL: "https://music.163.com/#/song?id=404", Duration: time.Minute}, "", nil)
|
||||
|
||||
+1
-1
@@ -126,7 +126,7 @@ Authorization: Bearer $alice
|
||||
{"kind": "event", "type": "playback.updated", "term": 1, "seq": 13, "data": {"nowPlaying": {
|
||||
"entry": {"id": "2", "track": {"source": "netease", "id": "185811"}, "meta": {"title": "七里香", "artist": "周杰伦", "album": "七里香", "coverUrl": "https://p1.music.126.net/qilixiang.jpg", "pageUrl": "https://music.163.com/#/song?id=185811", "durationMs": 299000},
|
||||
"by": {"id": "$aliceId", "name": "$aliceName", "avatarHash": ""}, "auto": false, "likes": 1, "status": "ready"},
|
||||
"mediaUrl": "/media/netease/185811", "startedAtMs": 1, "durationMs": 299000, "gain": 1, "lyric": ""}}}
|
||||
"mediaUrl": "/media/netease/185811", "startedAtMs": 1, "durationMs": 299000, "gain": 1, "lyric": "", "lyricWords": "[1000,500](1000,500,0)窗", "lyricTrans": "[00:01.00]Window"}}}
|
||||
-- receive alice --
|
||||
{"kind": "response", "type": "playback.voteSkip", "id": "v1", "data": {"votes": 1, "required": 1, "skipped": true}}
|
||||
|
||||
|
||||
+3
-1
@@ -171,7 +171,9 @@
|
||||
"startedAtMs": {"type": "integer"},
|
||||
"durationMs": {"type": "integer", "description": "准备结果中的时长"},
|
||||
"gain": {"type": "number", "description": "线性增益系数"},
|
||||
"lyric": {"type": "string"}
|
||||
"lyric": {"type": "string"},
|
||||
"lyricWords": {"type": "string", "description": "逐字歌词,每行形如 [行起始ms,行时长ms](字起始ms,字时长ms,0)字,时间为绝对毫秒"},
|
||||
"lyricTrans": {"type": "string", "description": "翻译歌词,行级 LRC,时间戳与原文的行起点对应"}
|
||||
},
|
||||
"required": ["entry", "mediaUrl", "startedAtMs", "durationMs", "gain", "lyric"]
|
||||
},
|
||||
|
||||
@@ -631,6 +631,14 @@
|
||||
"lyric": {
|
||||
"type": "string"
|
||||
},
|
||||
"lyricTrans": {
|
||||
"description": "翻译歌词,行级 LRC,时间戳与原文的行起点对应",
|
||||
"type": "string"
|
||||
},
|
||||
"lyricWords": {
|
||||
"description": "逐字歌词,每行形如 [行起始ms,行时长ms](字起始ms,字时长ms,0)字,时间为绝对毫秒",
|
||||
"type": "string"
|
||||
},
|
||||
"mediaUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -1335,7 +1343,7 @@
|
||||
},
|
||||
"info": {
|
||||
"title": "Alisten",
|
||||
"version": "09dbf7ea495db969"
|
||||
"version": "473ee31f5ef912b4"
|
||||
},
|
||||
"openapi": "3.1.0",
|
||||
"paths": {
|
||||
|
||||
@@ -4,7 +4,7 @@ package protocol
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
const Version = "09dbf7ea495db969"
|
||||
const Version = "473ee31f5ef912b4"
|
||||
|
||||
type APIKey struct {
|
||||
Key string `json:"key"`
|
||||
@@ -255,6 +255,8 @@ type NowPlaying struct {
|
||||
Entry Entry `json:"entry"`
|
||||
Gain float64 `json:"gain"`
|
||||
Lyric string `json:"lyric"`
|
||||
LyricTrans *string `json:"lyricTrans,omitempty"`
|
||||
LyricWords *string `json:"lyricWords,omitempty"`
|
||||
MediaURL string `json:"mediaUrl"`
|
||||
StartedAtMs int64 `json:"startedAtMs"`
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by protocolgen from protocol/idl.json; DO NOT EDIT.
|
||||
|
||||
export const PROTOCOL_VERSION = '09dbf7ea495db969'
|
||||
export const PROTOCOL_VERSION = '473ee31f5ef912b4'
|
||||
|
||||
export interface APIKey {
|
||||
key: string
|
||||
@@ -67,6 +67,8 @@ export interface NowPlaying {
|
||||
entry: Entry
|
||||
gain: number
|
||||
lyric: string
|
||||
lyricTrans?: string
|
||||
lyricWords?: string
|
||||
mediaUrl: string
|
||||
startedAtMs: number
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user