package controller import ( "errors" "log" "strings" socketio "github.com/googollee/go-socket.io" "github.com/hyperzlib/isekai-remote-playback/context" "github.com/hyperzlib/isekai-remote-playback/events" "github.com/hyperzlib/isekai-remote-playback/store" "github.com/hyperzlib/isekai-remote-playback/utils" "github.com/pochard/commons/randstr" ) type ClientSocketContext struct { Token string User string PairCode string } type ClientSocketController struct { SocketController } var clientSocketControllerInstance *ClientSocketController var ErrPlayerNotExists = errors.New("code not exists") var ErrParamsInvalid = errors.New("params invalid") func NewClientSocketController(basePath string, ctx *context.ServerContext) (*ClientSocketController, error) { instance := &ClientSocketController{ SocketController{ basePath: basePath, sockets: ctx.Sockets, events: ctx.EventSource, context: ctx, storeModel: store.NewStoreModel(), }, } err := instance.Initialize() if err != nil { return nil, err } clientSocketControllerInstance = instance return instance, nil } func (c *ClientSocketController) Initialize() error { server := c.sockets server.OnConnect(c.basePath, func(s socketio.Conn) error { s.SetContext(&ClientSocketContext{}) s.Join("clients") return nil }) server.OnDisconnect(c.basePath, c.OnDisconnect) server.OnEvent(c.basePath, "token:refresh", c.RefreshToken) server.OnEvent(c.basePath, "token:set", c.SetToken) server.OnEvent(c.basePath, "code:get", c.GetCode) server.OnEvent(c.basePath, "player:name:set", c.SetPlayerName) server.OnEvent(c.basePath, "player:play", c.SetPlayerName) server.OnError(c.basePath, func(s socketio.Conn, e error) { log.Println("meet error:", e) }) c.events.AddListener("bindUser", c.OnBindUser) c.events.AddListener("playMusic", c.OnPlayMusic) c.events.AddListener("playSoundEffect", c.OnPlaySoundEffect) c.events.AddListener("playVoice", c.OnPlayVoice) c.events.AddListener("stopPlay", c.OnStopPlay) return nil } // SwitchRoom 切换room,并退出所有同前缀的room func (c *ClientSocketController) SwitchRoom(socket socketio.Conn, prefix string, room string) { prefixWithColon := prefix + ":" for _, roomName := range socket.Rooms() { if strings.HasPrefix(roomName, prefixWithColon) { socket.Leave(roomName) } } if room != "" { socket.Join(prefixWithColon + ":" + room) } } // OnDisconnect 处理连接断开 func (c *ClientSocketController) OnDisconnect(socket socketio.Conn, reason string) { ctx := GetContext[ClientSocketContext](socket) // 删除存储中的code if ctx.Token != "" { if ctx.PairCode != "" { c.storeModel.RemoveCodeInfo(ctx.PairCode) } playerInfo := c.storeModel.GetPlayerInfo(ctx.Token) if playerInfo != nil { playerInfo.PairCode = "" playerInfo.IsOnline = false c.storeModel.SetPlayerInfo(ctx.Token, playerInfo) } } } // SetToken 设置token (客户端登录) func (c *ClientSocketController) SetToken(socket socketio.Conn, token string) { ctx := GetContext[ClientSocketContext](socket) if token != "" { ctx.Token = token playerInfo := c.storeModel.GetPlayerInfo(token) if playerInfo == nil { socket.Emit("token:expired") return } if playerInfo.BoundUser != "" { ctx.User = playerInfo.BoundUser userName := playerInfo.BoundUser userInfo := c.storeModel.GetUserInfo(playerInfo.BoundUser) if userInfo != nil { userName = userInfo.DisplayName } // 通知客户端 socket.Emit("user:update", ctx.User, userName) // 加入房间 c.SwitchRoom(socket, "user", playerInfo.BoundUser) } if playerInfo.PairCode != "" { // 删除存储中的code c.storeModel.RemoveCodeInfo(playerInfo.PairCode) playerInfo.PairCode = "" c.storeModel.SetPlayerInfo(ctx.Token, playerInfo) } playerInfo.IsOnline = true c.storeModel.SetPlayerInfo(ctx.Token, playerInfo) socket.Emit("token:update", token) } } // RefreshToken 刷新token func (c *ClientSocketController) RefreshToken(socket socketio.Conn) { ctx := GetContext[ClientSocketContext](socket) oldToken := ctx.Token var playerInfo *store.PlayerInfo if oldToken != "" { playerInfo = c.storeModel.GetPlayerInfo(oldToken) } newToken := randstr.RandomAlphanumeric(32) ctx.Token = newToken if playerInfo == nil { playerInfo = new(store.PlayerInfo) playerInfo.Name = utils.GetPlayerNameFromUserAgent(socket.RemoteHeader().Get("User-Agent")) } else { c.storeModel.RemovePlayerInfo(oldToken) } if playerInfo.PairCode != "" { // 删除存储中的code c.storeModel.RemoveCodeInfo(playerInfo.PairCode) playerInfo.PairCode = "" } c.storeModel.SetPlayerInfo(newToken, playerInfo) socket.Emit("token:update", newToken) } // GetCode 获取连接码 func (c *ClientSocketController) GetCode(socket socketio.Conn) { ctx := GetContext[ClientSocketContext](socket) newCode := randstr.RandomNumeric(6) socket.Emit("code:update", newCode) ctx.PairCode = newCode } // SetPlayerName 设置播放器设备名 func (c *ClientSocketController) SetPlayerName(socket socketio.Conn, name string) { ctx := GetContext[ClientSocketContext](socket) playerInfo := c.storeModel.GetPlayerInfo(ctx.Token) if playerInfo == nil { return } playerInfo.Name = name c.storeModel.SetPlayerInfo(ctx.Token, playerInfo) socket.Emit("player:name:update", name) } // PingbackPlay 播放器开始播放 func (c *ClientSocketController) PingbackPlay(socket socketio.Conn, currentTime int64, musicId string, playTime int64) { ctx := GetContext[ClientSocketContext](socket) c.events.EmitEvent("playerStartPlay", &events.PlayerStartPlay{ User: ctx.User, MusicId: musicId, CurrentTime: currentTime, PlayTime: playTime, }) } // PingbackPause 播放器暂停 func (c *ClientSocketController) PingbackPause(socket socketio.Conn, currentTime int64, musicId string, playTime int64) { ctx := GetContext[ClientSocketContext](socket) c.events.EmitEvent("playerPause", &events.PlayerPause{ User: ctx.User, MusicId: musicId, CurrentTime: currentTime, PlayTime: playTime, }) } // PingbackStop 播放器停止 func (c *ClientSocketController) PingbackStop(socket socketio.Conn, musicId string, isEnd bool) { ctx := GetContext[ClientSocketContext](socket) c.events.EmitEvent("playerStop", &events.PlayerStop{ User: ctx.User, MusicId: musicId, }) } // BindPlayer 用户触发绑定播放器 func (c *ClientSocketController) BindPlayer(user string, code string) (string, error) { // code为空则不绑定 if code == "" { return "", ErrParamsInvalid } token := "" c.sockets.ForEach("/", "clients", func(socket socketio.Conn) { ctx := GetContext[ClientSocketContext](socket) if ctx.PairCode == code { // 找到需要绑定的客户端 token = ctx.Token c.events.EmitEvent("bindUser", &events.BindUserEvent{ Token: token, User: user, }) } }) if token != "" { return token, nil } else { return "", ErrPlayerNotExists } } func (c *ClientSocketController) OnBindUser(event events.BindUserEvent) { userName := event.User userInfo := c.storeModel.GetUserInfo(event.User) if userInfo == nil { log.Println("Cannot find user info for user name '" + event.User + "' while bind player") return } userName = userInfo.DisplayName playerInfo := c.storeModel.GetPlayerInfo(event.Token) if playerInfo == nil { log.Println("Cannot find player info for token '" + event.Token + "' while bind player") return } // 存储信息 userInfo.BoundPlayer = append(userInfo.BoundPlayer, event.Token) playerInfo.BoundUser = event.User c.storeModel.SetUserInfo(event.User, userInfo) c.storeModel.SetPlayerInfo(event.Token, playerInfo) // 通知客户端 c.sockets.ForEach("/", "", func(socket socketio.Conn) { ctx := GetContext[ClientSocketContext](socket) if ctx.Token == event.Token { socket.Emit("user:update", event.User, userName) // 加入房间 c.SwitchRoom(socket, "user", event.User) } }) } // OnPlayMusic 用户触发播放音乐 func (c *ClientSocketController) OnPlayMusic(eventObj ...interface{}) { event, err := utils.GetEvent[events.PlayMusicEvent](eventObj) if err != nil { log.Panicln("Cannot get event in OnPlayMusic:", err.Error()) } type PlayMusicResponse struct { MusicId string `json:"music_id"` Src map[string]string `json:"src"` Name string `json:"name"` LyricUrl string `json:"lyric_url"` ImageUrl string `json:"image_url"` Loop bool `json:"loop"` Pingback bool `json:"pingback"` } res := &PlayMusicResponse{ Src: event.Src, Name: event.Name, LyricUrl: event.LyricUrl, ImageUrl: event.ImageUrl, Loop: event.Loop, Pingback: event.Pingback, } c.sockets.BroadcastToRoom("/", "user"+event.User, "music:play", res) } // OnPlaySoundEffect 用户触发播放音效 func (c *ClientSocketController) OnPlaySoundEffect(eventObj ...interface{}) { } // OnPlayVoice 用户触发播放语音 func (c *ClientSocketController) OnPlayVoice(eventObj ...interface{}) { } // OnStopPlay 用户触发停止播放 func (c *ClientSocketController) OnStopPlay(eventObj ...interface{}) { }