mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-08-09 11:57:13 +03:00
Add call protocol, Rmux. Update AmneziaWG. Fixes and improvements
This commit is contained in:
341
transport/call/telemost/api.go
Normal file
341
transport/call/telemost/api.go
Normal file
@@ -0,0 +1,341 @@
|
||||
package telemost
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
mathrand "math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pion/interceptor"
|
||||
"github.com/pion/webrtc/v4"
|
||||
"github.com/sagernet/sing-box/transport/call/common"
|
||||
)
|
||||
|
||||
const (
|
||||
APIBase = "https://cloud-api.yandex.ru/telemost_front/v2/telemost"
|
||||
Origin = "https://telemost.yandex.ru"
|
||||
)
|
||||
|
||||
var CapabilitiesOffer = map[string][]string{
|
||||
"offerAnswerMode": {"SEPARATE"},
|
||||
"initialSubscriberOffer": {"ON_HELLO"},
|
||||
"slotsMode": {"FROM_CONTROLLER"},
|
||||
"simulcastMode": {"DISABLED", "STATIC"},
|
||||
"selfVadStatus": {"FROM_SERVER", "FROM_CLIENT"},
|
||||
"dataChannelSharing": {"TO_RTP"},
|
||||
"videoEncoderConfig": {"NO_CONFIG", "ONLY_INIT_CONFIG", "RUNTIME_CONFIG"},
|
||||
"dataChannelVideoCodec": {"VP8", "UNIQUE_CODEC_FROM_TRACK_DESCRIPTION"},
|
||||
"bandwidthLimitationReason": {"BANDWIDTH_REASON_DISABLED", "BANDWIDTH_REASON_ENABLED"},
|
||||
"sdkDefaultDeviceManagement": {"SDK_DEFAULT_DEVICE_MANAGEMENT_DISABLED", "SDK_DEFAULT_DEVICE_MANAGEMENT_ENABLED"},
|
||||
"joinOrderLayout": {"JOIN_ORDER_LAYOUT_DISABLED", "JOIN_ORDER_LAYOUT_ENABLED"},
|
||||
"pinLayout": {"PIN_LAYOUT_DISABLED"},
|
||||
"sendSelfViewVideoSlot": {"SEND_SELF_VIEW_VIDEO_SLOT_DISABLED", "SEND_SELF_VIEW_VIDEO_SLOT_ENABLED"},
|
||||
"serverLayoutTransition": {"SERVER_LAYOUT_TRANSITION_DISABLED"},
|
||||
"sdkPublisherOptimizeBitrate": {"SDK_PUBLISHER_OPTIMIZE_BITRATE_DISABLED", "SDK_PUBLISHER_OPTIMIZE_BITRATE_FULL", "SDK_PUBLISHER_OPTIMIZE_BITRATE_ONLY_SELF"},
|
||||
"sdkNetworkLostDetection": {"SDK_NETWORK_LOST_DETECTION_DISABLED"},
|
||||
"sdkNetworkPathMonitor": {"SDK_NETWORK_PATH_MONITOR_DISABLED"},
|
||||
"publisherVp9": {"PUBLISH_VP9_DISABLED", "PUBLISH_VP9_ENABLED"},
|
||||
"svcMode": {"SVC_MODE_DISABLED", "SVC_MODE_L3T3", "SVC_MODE_L3T3_KEY"},
|
||||
"subscriberOfferAsyncAck": {"SUBSCRIBER_OFFER_ASYNC_ACK_DISABLED", "SUBSCRIBER_OFFER_ASYNC_ACK_ENABLED"},
|
||||
"subscriberDtlsPassiveMode": {"SUBSCRIBER_DTLS_PASSIVE_MODE_DISABLED", "SUBSCRIBER_DTLS_PASSIVE_MODE_ENABLED"},
|
||||
"androidBluetoothRoutingFix": {"ANDROID_BLUETOOTH_ROUTING_FIX_DISABLED"},
|
||||
"fixedIceCandidatesPoolSize": {"FIXED_ICE_CANDIDATES_POOL_SIZE_DISABLED"},
|
||||
"sdkAndroidTelecomIntegration": {"SDK_ANDROID_TELECOM_INTEGRATION_DISABLED"},
|
||||
"setActiveCodecsMode": {"SET_ACTIVE_CODECS_MODE_DISABLED", "SET_ACTIVE_CODECS_MODE_VIDEO_ONLY"},
|
||||
"publisherOpusDred": {"PUBLISHER_OPUS_DRED_DISABLED"},
|
||||
"publisherOpusLowBitrate": {"PUBLISHER_OPUS_LOW_BITRATE_DISABLED"},
|
||||
"sdkAndroidDestroySessionOnTaskRemoved": {"SDK_ANDROID_DESTROY_SESSION_ON_TASK_REMOVED_DISABLED"},
|
||||
"svcModes": {"FALSE"},
|
||||
"reportTelemetryModes": {"TRUE"},
|
||||
"keepDefaultDevicesModes": {"FALSE"},
|
||||
}
|
||||
|
||||
var StartupSlotSizes = [][][2]int{
|
||||
{{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}},
|
||||
{{464, 261}, {464, 261}, {464, 261}, {336, 189}, {272, 153}, {272, 153}, {272, 153}, {272, 153}, {224, 126}, {224, 126}, {224, 126}, {224, 126}},
|
||||
{{464, 261}, {464, 261}, {464, 261}, {336, 189}, {272, 153}, {272, 153}, {272, 153}, {272, 153}, {224, 126}, {224, 126}, {224, 126}, {224, 126}},
|
||||
{{672, 378}, {672, 378}, {464, 261}, {336, 189}, {320, 180}, {320, 180}, {320, 180}, {320, 180}, {272, 153}, {272, 153}, {224, 126}, {224, 126}},
|
||||
}
|
||||
|
||||
type SlotBindEvent struct {
|
||||
Slot int
|
||||
ParticipantID string
|
||||
Mid string
|
||||
Reason string
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
HTTP *http.Client
|
||||
Cookie string
|
||||
UserAgent string
|
||||
AppVersion string
|
||||
InstanceID string
|
||||
}
|
||||
|
||||
func (c *Client) Do(method, path string, body interface{}) ([]byte, int, error) {
|
||||
var bodyReader io.Reader
|
||||
if body != nil {
|
||||
data, _ := json.Marshal(body)
|
||||
bodyReader = strings.NewReader(string(data))
|
||||
}
|
||||
req, err := http.NewRequest(method, APIBase+path, bodyReader)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
ua := c.UserAgent
|
||||
if ua == "" {
|
||||
ua = common.UserAgent
|
||||
}
|
||||
instanceID := c.InstanceID
|
||||
if instanceID == "" {
|
||||
instanceID = uuid.New().String()
|
||||
}
|
||||
req.Header.Set("User-Agent", ua)
|
||||
req.Header.Set("Origin", Origin)
|
||||
req.Header.Set("Referer", Origin+"/")
|
||||
req.Header.Set("Client-Instance-Id", instanceID)
|
||||
if c.Cookie != "" {
|
||||
req.Header.Set("Cookie", c.Cookie)
|
||||
}
|
||||
if c.AppVersion != "" {
|
||||
req.Header.Set("X-Telemost-Client-Version", c.AppVersion)
|
||||
}
|
||||
if body != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
client := c.HTTP
|
||||
if client == nil {
|
||||
client = http.DefaultClient
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
return data, resp.StatusCode, err
|
||||
}
|
||||
|
||||
func (c *Client) TMRequest(method, path string) ([]byte, int, error) {
|
||||
return c.Do(method, path, nil)
|
||||
}
|
||||
|
||||
func (c *Client) RequestStates(joinURI, peerID string) error {
|
||||
confURL := url.QueryEscape(joinURI)
|
||||
body := map[string]interface{}{
|
||||
"peers": []map[string]string{{"peer_id": peerID}},
|
||||
"permissions": map[string]interface{}{},
|
||||
"conference": map[string]interface{}{"version": -1},
|
||||
}
|
||||
r, status, err := c.Do("POST", "/conferences/"+confURL+"/request-states", body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if status != 200 {
|
||||
return fmt.Errorf("status %d: %s", status, string(r))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewAPI(settingEngine *webrtc.SettingEngine) (*webrtc.API, error) {
|
||||
mediaEngine := &webrtc.MediaEngine{}
|
||||
if err := mediaEngine.RegisterDefaultCodecs(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, uri := range []string{
|
||||
"urn:ietf:params:rtp-hdrext:toffset",
|
||||
"http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time",
|
||||
"urn:3gpp:video-orientation",
|
||||
"http://www.webrtc.org/experiments/rtp-hdrext/playout-delay",
|
||||
"http://www.webrtc.org/experiments/rtp-hdrext/video-content-type",
|
||||
"http://www.webrtc.org/experiments/rtp-hdrext/video-timing",
|
||||
"http://www.webrtc.org/experiments/rtp-hdrext/color-space",
|
||||
} {
|
||||
if err := mediaEngine.RegisterHeaderExtension(
|
||||
webrtc.RTPHeaderExtensionCapability{URI: uri},
|
||||
webrtc.RTPCodecTypeVideo,
|
||||
); err != nil {
|
||||
return nil, fmt.Errorf("register header extension %s: %w", uri, err)
|
||||
}
|
||||
}
|
||||
registry := &interceptor.Registry{}
|
||||
if err := webrtc.RegisterDefaultInterceptors(mediaEngine, registry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts := []func(*webrtc.API){
|
||||
webrtc.WithMediaEngine(mediaEngine),
|
||||
webrtc.WithInterceptorRegistry(registry),
|
||||
}
|
||||
if settingEngine != nil {
|
||||
opts = append(opts, webrtc.WithSettingEngine(*settingEngine))
|
||||
}
|
||||
return webrtc.NewAPI(opts...), nil
|
||||
}
|
||||
|
||||
func NewPeerConnection(config webrtc.Configuration) (*webrtc.PeerConnection, error) {
|
||||
api, err := NewAPI(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return api.NewPeerConnection(config)
|
||||
}
|
||||
|
||||
func MungeSDPAddVideoContent(sdp string) string {
|
||||
lines := strings.Split(sdp, "\r\n")
|
||||
out := make([]string, 0, len(lines)+4)
|
||||
inVideo := false
|
||||
inserted := false
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "m=") {
|
||||
if inVideo && !inserted {
|
||||
out = append(out, "a=content:speaker,main")
|
||||
inserted = true
|
||||
}
|
||||
inVideo = strings.HasPrefix(line, "m=video")
|
||||
inserted = false
|
||||
}
|
||||
out = append(out, line)
|
||||
if inVideo && !inserted && strings.HasPrefix(line, "a=mid:") {
|
||||
out = append(out, "a=content:speaker,main")
|
||||
inserted = true
|
||||
}
|
||||
}
|
||||
return strings.Join(out, "\r\n")
|
||||
}
|
||||
|
||||
func SlotsConfigBindings(v interface{}) []SlotBindEvent {
|
||||
m, ok := v.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
slots, _ := m["slots"].([]interface{})
|
||||
var out []SlotBindEvent
|
||||
for idx, s := range slots {
|
||||
sm, _ := s.(map[string]interface{})
|
||||
if pv, _ := sm["participantVideoByMid"].(map[string]interface{}); pv != nil {
|
||||
pid, _ := pv["participantId"].(string)
|
||||
mid, _ := pv["mid"].(string)
|
||||
reason, _ := pv["limitationReason"].(string)
|
||||
out = append(out, SlotBindEvent{Slot: idx, ParticipantID: pid, Mid: mid, Reason: reason})
|
||||
continue
|
||||
}
|
||||
if p, _ := sm["participant"].(map[string]interface{}); p != nil {
|
||||
pid, _ := p["participantId"].(string)
|
||||
out = append(out, SlotBindEvent{Slot: idx, ParticipantID: pid})
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func BriefJSON(v interface{}) string {
|
||||
const max = 240
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("<json err: %v>", err)
|
||||
}
|
||||
if len(b) > max {
|
||||
return string(b[:max]) + "...(+" + fmt.Sprintf("%d", len(b)-max) + "B)"
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func SetSlotsMessage(key int) map[string]interface{} {
|
||||
rnd := mathrand.New(mathrand.NewSource(time.Now().UnixNano()))
|
||||
return slotsMessageWithSizes(key, StartupSlotSizes[len(StartupSlotSizes)-1], rnd)
|
||||
}
|
||||
|
||||
func StartupSetSlotsMessage(i, key int) map[string]interface{} {
|
||||
rnd := mathrand.New(mathrand.NewSource(time.Now().UnixNano() + int64(i)))
|
||||
return slotsMessageWithSizes(key, StartupSlotSizes[i], rnd)
|
||||
}
|
||||
|
||||
func SetSlotsOffsetMessage(offset int) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"uid": uuid.New().String(),
|
||||
"setSlotsOffset": map[string]interface{}{"offset": offset},
|
||||
}
|
||||
}
|
||||
|
||||
func SdkCodecsInfoMessage() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"uid": uuid.New().String(),
|
||||
"sdkCodecsInfo": map[string]interface{}{
|
||||
"vp8": map[string]interface{}{
|
||||
"supported": "CODEC_FEATURE_SUPPORTED",
|
||||
"hwDecode": "CODEC_FEATURE_NOT_SUPPORTED",
|
||||
"hwEncode": "CODEC_FEATURE_NOT_SUPPORTED",
|
||||
"isoString": "vp8",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func UpdatePublisherTrackDescriptionMessage(pc *webrtc.PeerConnection, audioLabel, videoLabel string) map[string]interface{} {
|
||||
descs := []map[string]interface{}{}
|
||||
for _, tr := range pc.GetTransceivers() {
|
||||
sender := tr.Sender()
|
||||
if sender == nil || sender.Track() == nil {
|
||||
continue
|
||||
}
|
||||
kind := strings.ToUpper(sender.Track().Kind().String())
|
||||
mid := tr.Mid()
|
||||
label := videoLabel
|
||||
groupId := 2
|
||||
if kind == "AUDIO" {
|
||||
label = audioLabel
|
||||
groupId = 1
|
||||
}
|
||||
descs = append(descs, map[string]interface{}{
|
||||
"mid": mid,
|
||||
"transceiverMid": mid,
|
||||
"kind": kind,
|
||||
"priority": 0,
|
||||
"label": label,
|
||||
"codecs": map[string]interface{}{},
|
||||
"groupId": groupId,
|
||||
"description": "",
|
||||
})
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"uid": uuid.New().String(),
|
||||
"updatePublisherTrackDescription": map[string]interface{}{
|
||||
"publisherTrackDescriptions": descs,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func jitterSize(width int, rnd *mathrand.Rand) (int, int) {
|
||||
if width == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
w := width + rnd.Intn(11) - 5
|
||||
return w, w * 9 / 16
|
||||
}
|
||||
|
||||
func slotsMessageWithSizes(key int, template [][2]int, rnd *mathrand.Rand) map[string]interface{} {
|
||||
slots := make([]map[string]interface{}, len(template))
|
||||
for i, wh := range template {
|
||||
w, h := wh[0], wh[1]
|
||||
if rnd != nil {
|
||||
w, h = jitterSize(wh[0], rnd)
|
||||
}
|
||||
slots[i] = map[string]interface{}{"width": w, "height": h}
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"uid": uuid.New().String(),
|
||||
"setSlots": map[string]interface{}{
|
||||
"slots": slots,
|
||||
"audioSlotsCount": 0,
|
||||
"key": key,
|
||||
"shutdownAllVideo": nil,
|
||||
"withSelfView": true,
|
||||
"selfViewVisibility": "ON_LOADING_THEN_SHOW",
|
||||
"gridConfig": map[string]interface{}{},
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user