音视频通话

pull/3727/head
hawklin2017 2 months ago
parent e6451179d6
commit 4b9412f685

@ -0,0 +1,17 @@
port: 7880
rtc:
tcp_port: 7881
port_range_start: 50000
port_range_end: 60000
use_external_ip: true
redis:
address: redis:6379
password: openIM123
# LiveKit 要求 API secret 至少 32 字符;生产环境请替换为强随机串并与 openim-rpc-rtc.yml 中 apiSecret 一致
keys:
devkey: openim-livekit-default-secret-32chars-min
logging:
level: info

@ -18,14 +18,14 @@ prometheus:
liveKit:
# LiveKit server address reachable from the RTC service (internal/backend address)
# Example: http://livekit:7880
internalAddress: http://localhost:7880
# When deployed via docker-compose, use the service name 'livekit'
internalAddress: http://livekit:7880
# LiveKit server address reachable from clients (external/public address)
# Example: wss://livekit.example.com
externalAddress: ws://localhost:7880
# Production should use wss://livekit.example.com with TLS
externalAddress: ws://192.168.1.91:7880
# LiveKit API key (configured in your LiveKit server)
apiKey: devkey
# LiveKit API secret (configured in your LiveKit server)
apiSecret: secret
# LiveKit API secret(须 ≥32 字符,须与 config/livekit.yaml keys 中对应值一致)
apiSecret: openim-livekit-default-secret-32chars-min
# Token expiry in seconds (default: 3600 = 1 hour)
tokenExpiry: 3600

@ -163,6 +163,24 @@ services:
networks:
- openim
livekit:
image: "${LIVEKIT_IMAGE:-livekit/livekit-server:latest}"
container_name: livekit
restart: always
ports:
- "7880:7880"
- "7881:7881"
- "50000-50100:50000-50100/udp"
volumes:
- ./config/livekit.yaml:/etc/livekit.yaml
command: --config /etc/livekit.yaml --node-ip=0.0.0.0
depends_on:
- redis
environment:
TZ: Asia/Shanghai
networks:
- openim
minio:
image: "${MINIO_IMAGE}"
ports:

@ -108,3 +108,7 @@ func (p *PrometheusDiscoveryApi) MessageGateway(c *gin.Context) {
func (p *PrometheusDiscoveryApi) MessageTransfer(c *gin.Context) {
p.discovery(c, prommetrics.MessageTransferKeyName)
}
func (p *PrometheusDiscoveryApi) Rtc(c *gin.Context) {
p.discovery(c, p.config.Share.RpcRegisterName.Rtc)
}

@ -12,6 +12,7 @@ import (
"github.com/openimsdk/protocol/group"
"github.com/openimsdk/protocol/msg"
"github.com/openimsdk/protocol/relation"
"github.com/openimsdk/protocol/rtc"
"github.com/openimsdk/protocol/third"
"github.com/openimsdk/protocol/user"
@ -103,6 +104,10 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, co
if err != nil {
return nil, err
}
rtcConn, err := client.GetConn(ctx, config.Share.RpcRegisterName.Rtc)
if err != nil {
return nil, err
}
gin.SetMode(gin.ReleaseMode)
r := gin.New()
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
@ -301,6 +306,20 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, co
captchaGroup.POST("/verify", cp.VerifyCaptcha)
}
{
rc := NewRtcApi(rtc.NewRtcServiceClient(rtcConn))
rtcGroup := r.Group("/rtc")
rtcGroup.POST("/signal_message_assemble", rc.SignalMessageAssemble)
rtcGroup.POST("/signal_get_room_by_group_id", rc.SignalGetRoomByGroupID)
rtcGroup.POST("/signal_get_token_by_room_id", rc.SignalGetTokenByRoomID)
rtcGroup.POST("/signal_get_rooms", rc.SignalGetRooms)
rtcGroup.POST("/get_signal_invitation_info", rc.GetSignalInvitationInfo)
rtcGroup.POST("/get_signal_invitation_info_start_app", rc.GetSignalInvitationInfoStartApp)
rtcGroup.POST("/signal_send_custom_signal", rc.SignalSendCustomSignal)
rtcGroup.POST("/get_signal_invitation_records", rc.GetSignalInvitationRecords)
rtcGroup.POST("/delete_signal_records", rc.DeleteSignalRecords)
}
{
statisticsGroup := r.Group("/statistics")
statisticsGroup.POST("/user/register", u.UserRegisterCount)
@ -330,6 +349,7 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, co
proDiscoveryGroup.GET("/push", pd.Push)
proDiscoveryGroup.GET("/msg_gateway", pd.MessageGateway)
proDiscoveryGroup.GET("/msg_transfer", pd.MessageTransfer)
proDiscoveryGroup.GET("/rtc", pd.Rtc)
}
return r, nil
}

@ -0,0 +1,65 @@
// Copyright © 2024 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package api
import (
"github.com/gin-gonic/gin"
"github.com/openimsdk/protocol/rtc"
"github.com/openimsdk/tools/a2r"
)
type RtcApi struct {
Client rtc.RtcServiceClient
}
func NewRtcApi(client rtc.RtcServiceClient) RtcApi {
return RtcApi{Client: client}
}
func (o *RtcApi) SignalMessageAssemble(c *gin.Context) {
a2r.Call(c, rtc.RtcServiceClient.SignalMessageAssemble, o.Client)
}
func (o *RtcApi) SignalGetRoomByGroupID(c *gin.Context) {
a2r.Call(c, rtc.RtcServiceClient.SignalGetRoomByGroupID, o.Client)
}
func (o *RtcApi) SignalGetTokenByRoomID(c *gin.Context) {
a2r.Call(c, rtc.RtcServiceClient.SignalGetTokenByRoomID, o.Client)
}
func (o *RtcApi) SignalGetRooms(c *gin.Context) {
a2r.Call(c, rtc.RtcServiceClient.SignalGetRooms, o.Client)
}
func (o *RtcApi) GetSignalInvitationInfo(c *gin.Context) {
a2r.Call(c, rtc.RtcServiceClient.GetSignalInvitationInfo, o.Client)
}
func (o *RtcApi) GetSignalInvitationInfoStartApp(c *gin.Context) {
a2r.Call(c, rtc.RtcServiceClient.GetSignalInvitationInfoStartApp, o.Client)
}
func (o *RtcApi) SignalSendCustomSignal(c *gin.Context) {
a2r.Call(c, rtc.RtcServiceClient.SignalSendCustomSignal, o.Client)
}
func (o *RtcApi) GetSignalInvitationRecords(c *gin.Context) {
a2r.Call(c, rtc.RtcServiceClient.GetSignalInvitationRecords, o.Client)
}
func (o *RtcApi) DeleteSignalRecords(c *gin.Context) {
a2r.Call(c, rtc.RtcServiceClient.DeleteSignalRecords, o.Client)
}

@ -32,7 +32,7 @@ import (
"github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mcontext"
"github.com/openimsdk/tools/utils/datautil"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
)
// SignalMessageAssemble processes a signal request from the WebSocket gateway
@ -379,8 +379,8 @@ func (s *rtcServer) SignalSendCustomSignal(ctx context.Context, req *rtc.SignalS
if uid == opUserID {
continue
}
if err := s.sendSignalingNotification(ctx, opUserID, uid, int32(constant.SingleChatType), nil, content); err != nil {
log.ZWarn(ctx, "sendSignalingNotification customSignal failed", err, "to", uid)
if err := s.sendCustomSignalNotification(ctx, opUserID, uid, int32(constant.SingleChatType), content); err != nil {
log.ZWarn(ctx, "sendCustomSignalNotification failed", err, "to", uid)
}
}
return &rtc.SignalSendCustomSignalResp{}, nil
@ -462,10 +462,28 @@ func (s *rtcServer) sendSignalingNotification(ctx context.Context, sendID, recvI
return err
}
// marshalSignalReq serialises a SignalReq to JSON bytes using protojson,
// which correctly handles protobuf oneof fields for client-side parsing.
// sendCustomSignalNotification sends a CustomSignalNotification (1605) to a user.
func (s *rtcServer) sendCustomSignalNotification(ctx context.Context, sendID, recvID string, sessionType int32, content []byte) error {
now := time.Now().UnixMilli()
msgData := &sdkws.MsgData{
SendID: sendID,
RecvID: recvID,
SessionType: sessionType,
ContentType: int32(constant.CustomSignalNotification),
MsgFrom: int32(constant.SysMsgType),
Content: content,
CreateTime: now,
SendTime: now,
ServerMsgID: uuid.New().String(),
ClientMsgID: uuid.New().String(),
Options: make(map[string]bool),
}
_, err := s.msgClient.MsgClient.SendMsg(ctx, &pbmsg.SendMsgReq{MsgData: msgData})
return err
}
func marshalSignalReq(req *rtc.SignalReq) []byte {
b, _ := protojson.Marshal(req)
b, _ := proto.Marshal(req)
return b
}

@ -11,6 +11,7 @@ serviceBinaries:
openim-rpc-group: 1
openim-rpc-friend: 1
openim-rpc-msg: 1
openim-rpc-rtc: 1
openim-rpc-third: 1
toolBinaries:
- check-free-memory

Loading…
Cancel
Save