eos 음성채팅 지원 추가
This commit is contained in:
@ -894,3 +894,4 @@ func (gp *groupParty) DenyPartyInvitation(ctx wshandler.ApiCallContext) {
|
||||
Tag: []string{"MemberDocFull"},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
227
core/group_voice.go
Normal file
227
core/group_voice.go
Normal file
@ -0,0 +1,227 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"repositories.action2quare.com/ayo/gocommon"
|
||||
"repositories.action2quare.com/ayo/gocommon/logger"
|
||||
"repositories.action2quare.com/ayo/gocommon/wshandler"
|
||||
)
|
||||
|
||||
type eosauth struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
ExpiresIn int64 `json:"expires_in"`
|
||||
DeploymentId string `json:"deployment_id"`
|
||||
ProductId string `json:"product_id"`
|
||||
SandboxId string `json:"sandbox_id"`
|
||||
TokenType string `json:"token_type"`
|
||||
}
|
||||
|
||||
type groupVoice struct {
|
||||
rh *gocommon.RedisonHandler
|
||||
eosptr unsafe.Pointer
|
||||
}
|
||||
|
||||
func (gv *groupVoice) eosTokenRefresh(ctx context.Context) {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r != nil {
|
||||
logger.Error(r)
|
||||
}
|
||||
}()
|
||||
|
||||
endpoint := "https://api.epicgames.dev/auth/v1/oauth/token"
|
||||
auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", config.EosClientId, config.EosClientSecret)))
|
||||
body := bytes.NewBufferString("grant_type=client_credentials&deployment_id=" + config.EosDeploymentId)
|
||||
for {
|
||||
req, _ := http.NewRequest("POST", endpoint, body)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Basic %s", auth))
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
logger.Println("eosTokenRefresh failed. eos token reqeust err :", err)
|
||||
time.Sleep(time.Minute)
|
||||
continue
|
||||
}
|
||||
|
||||
var neweos eosauth
|
||||
err = json.NewDecoder(resp.Body).Decode(&neweos)
|
||||
resp.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
logger.Println("eosTokenRefresh failed. decode err :", err)
|
||||
return
|
||||
}
|
||||
|
||||
logger.Printf("eos access_token retreived : %s...", neweos.AccessToken[:20])
|
||||
atomic.StorePointer(&gv.eosptr, unsafe.Pointer(&neweos))
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
|
||||
case <-time.After(time.Duration(neweos.ExpiresIn-60) * time.Second):
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (gv *groupVoice) eosAuth() *eosauth {
|
||||
ptr := atomic.LoadPointer(&gv.eosptr)
|
||||
return (*eosauth)(ptr)
|
||||
}
|
||||
|
||||
func (gv *groupVoice) Initialize(tv *Tavern, ctx context.Context) error {
|
||||
gv.rh = tv.redison
|
||||
gv.eosptr = unsafe.Pointer(&eosauth{})
|
||||
|
||||
if len(config.EosClientId) == 0 {
|
||||
logger.Println("eos voice chat is disabled. 'eos_client_id' is empty")
|
||||
}
|
||||
if len(config.EosClientSecret) == 0 {
|
||||
logger.Println("eos voice chat is disabled. 'eos_client_secret' is empty")
|
||||
}
|
||||
if len(config.EosDeploymentId) == 0 {
|
||||
logger.Println("eos voice chat is disabled. 'eos_deployment_id' is empty")
|
||||
}
|
||||
|
||||
if len(config.EosClientId) > 0 && len(config.EosClientSecret) > 0 && len(config.EosDeploymentId) > 0 {
|
||||
go gv.eosTokenRefresh(ctx)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gv *groupVoice) ClientConnected(conn *websocket.Conn, callby *wshandler.Sender) {
|
||||
|
||||
}
|
||||
|
||||
func (gv *groupVoice) ClientDisconnected(msg string, callby *wshandler.Sender) {
|
||||
// vals, err := gv.rh.JSONGetString(callby.Accid.Hex(), "$.voice")
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
|
||||
// if len(vals) == 0 {
|
||||
// return
|
||||
// }
|
||||
|
||||
// switch vals[0] {
|
||||
// case "eos":
|
||||
// // TODO : Removing a Participant
|
||||
// // https://dev.epicgames.com/docs/web-api-ref/voice-web-api#removing-a-participant
|
||||
// }
|
||||
}
|
||||
|
||||
type eosRoomParticipantRequests struct {
|
||||
Puid string `json:"puid"`
|
||||
ClientIP string `json:"clientIP"`
|
||||
HardMuted bool `json:"hardMuted"`
|
||||
}
|
||||
|
||||
type eosRoomParticipants struct {
|
||||
Participants []eosRoomParticipantRequests `json:"participants"`
|
||||
}
|
||||
|
||||
func (gv *groupVoice) JoinVoiceChat(w http.ResponseWriter, r *http.Request) {
|
||||
var data struct {
|
||||
Gid string
|
||||
Mid string
|
||||
Service string
|
||||
Alias string
|
||||
}
|
||||
if err := gocommon.MakeDecoder(r).Decode(&data); err != nil {
|
||||
logger.Println("JoinVoiceChat failed. DecodeGob returns err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
switch data.Service {
|
||||
case "eos":
|
||||
// https://dev.epicgames.com/docs/web-api-ref/voice-web-api
|
||||
accessToken := gv.eosAuth().AccessToken
|
||||
if len(accessToken) == 0 {
|
||||
logger.Println("eos voice chat is not ready. access_token is empty")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
voiceendpoint := fmt.Sprintf("https://api.epicgames.dev/rtc/v1/%s/room/%s", config.EosDeploymentId, data.Gid)
|
||||
participants := eosRoomParticipants{
|
||||
Participants: []eosRoomParticipantRequests{
|
||||
{Puid: data.Mid},
|
||||
},
|
||||
}
|
||||
|
||||
body, _ := json.Marshal(participants)
|
||||
req, _ := http.NewRequest("POST", voiceendpoint, bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
logger.Println("join voice room failed. api.epicgames.dev return err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var result map[string]any
|
||||
json.NewDecoder(resp.Body).Decode(&result)
|
||||
resp.Body.Close()
|
||||
|
||||
result["client_id"] = config.EosClientId
|
||||
result["client_secret"] = config.EosClientSecret
|
||||
|
||||
par := result["participants"].([]any)[0]
|
||||
participant := par.(map[string]any)
|
||||
|
||||
channelCredentials := map[string]any{
|
||||
"override_userid": data.Mid,
|
||||
"client_base_url": result["clientBaseUrl"],
|
||||
"participant_token": participant["token"],
|
||||
}
|
||||
marshaled, _ := json.Marshal(channelCredentials)
|
||||
result["channel_credentials"] = base64.StdEncoding.EncodeToString(marshaled)
|
||||
|
||||
gocommon.MakeEncoder(w, r).Encode(result)
|
||||
}
|
||||
}
|
||||
|
||||
func (gv *groupVoice) LeaveVoiceChat(w http.ResponseWriter, r *http.Request) {
|
||||
var data struct {
|
||||
Gid string
|
||||
Mid string
|
||||
Service string
|
||||
}
|
||||
if err := gocommon.MakeDecoder(r).Decode(&data); err != nil {
|
||||
logger.Println("JoinVoiceChat failed. DecodeGob returns err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
switch data.Service {
|
||||
case "eos":
|
||||
voiceendpoint := fmt.Sprintf("https://api.epicgames.dev/rtc/v1/%s/room/%s/participants/%s", config.EosDeploymentId, data.Gid, data.Mid)
|
||||
accessToken := gv.eosAuth().AccessToken
|
||||
|
||||
req, _ := http.NewRequest("DELETE", voiceendpoint, nil)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
logger.Println("LeaveVoiceChat failed. err :", err)
|
||||
} else if resp.StatusCode != http.StatusOK {
|
||||
logger.Println("LeaveVoiceChat failed. status code :", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -28,6 +28,9 @@ type TavernConfig struct {
|
||||
Group map[string]configDocument `json:"tavern_group_types"`
|
||||
MaingateApiToken string `json:"maingate_api_token"`
|
||||
RedisURL string `json:"tavern_redis_url"`
|
||||
EosClientId string `json:"eos_client_id"`
|
||||
EosClientSecret string `json:"eos_client_secret"`
|
||||
EosDeploymentId string `json:"eos_deployment_id"`
|
||||
macAddr string
|
||||
}
|
||||
|
||||
@ -120,6 +123,13 @@ func (tv *Tavern) prepare(ctx context.Context) error {
|
||||
tv.httpApiBorker.AddHandler(gocommon.MakeHttpApiHandler(instant, "instant"))
|
||||
tv.wsh.AddHandler(wshandler.MakeWebsocketApiHandler(instant, "instant"))
|
||||
|
||||
voice := new(groupVoice)
|
||||
if err := voice.Initialize(tv, ctx); err != nil {
|
||||
return logger.ErrorWithCallStack(err)
|
||||
}
|
||||
tv.httpApiBorker.AddHandler(gocommon.MakeHttpApiHandler(voice, "voice"))
|
||||
tv.wsh.AddHandler(wshandler.MakeWebsocketApiHandler(voice, "voice"))
|
||||
|
||||
ccu = metric.NewMetric(metric.MetricGuage, "concurrent_user", "concurrent user count", map[string]string{"game": "lobby"})
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user