Skip to content

Commit a4d7fd0

Browse files
committed
Add support yandex source
1 parent ae8145f commit a4d7fd0

File tree

6 files changed

+425
-1
lines changed

6 files changed

+425
-1
lines changed

internal/yandex/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Yandex
2+
3+
Source for receiving stream from new [Yandex IP camera](https://alice.yandex.ru/smart-home/security/ipcamera).
4+
5+
## Get Yandex token
6+
7+
1. Install HomeAssistant integration [YandexStation](https://github.com/AlexxIT/YandexStation).
8+
2. Copy token from HomeAssistant config folder: `/config/.storage/core.config_entries`, key: `"x_token"`.
9+
10+
## Get device ID
11+
12+
1. Open this link in any browser: https://iot.quasar.yandex.ru/m/v3/user/devices
13+
2. Copy ID of your camera, key: `"id"`.
14+
15+
## Config examples
16+
17+
```yaml
18+
streams:
19+
yandex_stream: yandex:?x_token=XXXX&device_id=XXXX
20+
yandex_snapshot: yandex:?x_token=XXXX&device_id=XXXX&snapshot
21+
yandex_snapshot_custom_size: yandex:?x_token=XXXX&device_id=XXXX&snapshot=h=540
22+
```

internal/yandex/goloom.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package yandex
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"fmt"
7+
"time"
8+
9+
"github.com/AlexxIT/go2rtc/internal/webrtc"
10+
"github.com/AlexxIT/go2rtc/pkg/core"
11+
xwebrtc "github.com/AlexxIT/go2rtc/pkg/webrtc"
12+
"github.com/google/uuid"
13+
"github.com/gorilla/websocket"
14+
pion "github.com/pion/webrtc/v4"
15+
)
16+
17+
func goloomClient(serviceURL, serviceName, roomId, participantId, credentials string) (core.Producer, error) {
18+
conn, _, err := websocket.DefaultDialer.Dial(serviceURL, nil)
19+
if err != nil {
20+
return nil, err
21+
}
22+
defer func() {
23+
time.Sleep(time.Second)
24+
_ = conn.Close()
25+
}()
26+
27+
s := fmt.Sprintf(`{"hello": {
28+
"credentials":"%s","participantId":"%s","roomId":"%s","serviceName":"%s","sdkInitializationId":"%s",
29+
"capabilitiesOffer":{},"sendAudio":false,"sendSharing":false,"sendVideo":false,
30+
"sdkInfo":{"hwConcurrency":4,"implementation":"browser","version":"5.4.0"},
31+
"participantAttributes":{"description":"","name":"mike","role":"SPEAKER"},
32+
"participantMeta":{"description":"","name":"mike","role":"SPEAKER","sendAudio":false,"sendVideo":false}
33+
},"uid":"%s"}`,
34+
credentials, participantId, roomId, serviceName,
35+
uuid.NewString(), uuid.NewString(),
36+
)
37+
38+
err = conn.WriteMessage(websocket.TextMessage, []byte(s))
39+
if err != nil {
40+
return nil, err
41+
}
42+
43+
if _, _, err = conn.ReadMessage(); err != nil {
44+
return nil, err
45+
}
46+
47+
pc, err := webrtc.PeerConnection(true)
48+
if err != nil {
49+
return nil, err
50+
}
51+
52+
prod := xwebrtc.NewConn(pc)
53+
prod.FormatName = "yandex"
54+
prod.Mode = core.ModeActiveProducer
55+
prod.Protocol = "wss"
56+
57+
var connState core.Waiter
58+
59+
prod.Listen(func(msg any) {
60+
switch msg := msg.(type) {
61+
case pion.PeerConnectionState:
62+
switch msg {
63+
case pion.PeerConnectionStateConnecting:
64+
case pion.PeerConnectionStateConnected:
65+
connState.Done(nil)
66+
default:
67+
connState.Done(errors.New("webrtc: " + msg.String()))
68+
}
69+
}
70+
})
71+
72+
go func() {
73+
for {
74+
var msg map[string]json.RawMessage
75+
if err = conn.ReadJSON(&msg); err != nil {
76+
return
77+
}
78+
79+
for k, v := range msg {
80+
switch k {
81+
case "uid":
82+
continue
83+
case "serverHello":
84+
case "subscriberSdpOffer":
85+
var sdp subscriberSdp
86+
if err = json.Unmarshal(v, &sdp); err != nil {
87+
return
88+
}
89+
//log.Trace().Msgf("offer:\n%s", sdp.Sdp)
90+
if err = prod.SetOffer(sdp.Sdp); err != nil {
91+
return
92+
}
93+
if sdp.Sdp, err = prod.GetAnswer(); err != nil {
94+
return
95+
}
96+
//log.Trace().Msgf("answer:\n%s", sdp.Sdp)
97+
98+
var raw []byte
99+
if raw, err = json.Marshal(sdp); err != nil {
100+
return
101+
}
102+
s = fmt.Sprintf(`{"uid":"%s","subscriberSdpAnswer":%s}`, uuid.NewString(), raw)
103+
if err = conn.WriteMessage(websocket.TextMessage, []byte(s)); err != nil {
104+
return
105+
}
106+
case "webrtcIceCandidate":
107+
var candidate webrtcIceCandidate
108+
if err = json.Unmarshal(v, &candidate); err != nil {
109+
return
110+
}
111+
if err = prod.AddCandidate(candidate.Candidate); err != nil {
112+
return
113+
}
114+
}
115+
//log.Trace().Msgf("%s : %s", k, v)
116+
}
117+
118+
if msg["ack"] != nil {
119+
continue
120+
}
121+
122+
s = fmt.Sprintf(`{"uid":%s,"ack":{"status":{"code":"OK"}}}`, msg["uid"])
123+
if err = conn.WriteMessage(websocket.TextMessage, []byte(s)); err != nil {
124+
return
125+
}
126+
}
127+
}()
128+
129+
if err = connState.Wait(); err != nil {
130+
return nil, err
131+
}
132+
133+
s = fmt.Sprintf(`{"uid":"%s","setSlots":{"slots":[{"width":0,"height":0}],"audioSlotsCount":0,"key":1,"shutdownAllVideo":false,"withSelfView":false,"selfViewVisibility":"ON_LOADING_THEN_HIDE","gridConfig":{}}}`, uuid.NewString())
134+
if err = conn.WriteMessage(websocket.TextMessage, []byte(s)); err != nil {
135+
return nil, err
136+
}
137+
138+
return prod, nil
139+
}
140+
141+
type subscriberSdp struct {
142+
PcSeq int `json:"pcSeq"`
143+
Sdp string `json:"sdp"`
144+
}
145+
146+
type webrtcIceCandidate struct {
147+
PcSeq int `json:"pcSeq"`
148+
Target string `json:"target"`
149+
Candidate string `json:"candidate"`
150+
SdpMid string `json:"sdpMid"`
151+
SdpMlineIndex int `json:"sdpMlineIndex"`
152+
}

internal/yandex/yandex.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package yandex
2+
3+
import (
4+
"net/url"
5+
6+
"github.com/AlexxIT/go2rtc/internal/streams"
7+
"github.com/AlexxIT/go2rtc/pkg/core"
8+
"github.com/AlexxIT/go2rtc/pkg/yandex"
9+
)
10+
11+
func Init() {
12+
streams.HandleFunc("yandex", func(source string) (core.Producer, error) {
13+
u, err := url.Parse(source)
14+
if err != nil {
15+
return nil, err
16+
}
17+
18+
query := u.Query()
19+
token := query.Get("x_token")
20+
21+
session, err := yandex.GetSession(token)
22+
if err != nil {
23+
return nil, err
24+
}
25+
26+
deviceID := query.Get("device_id")
27+
28+
if query.Has("snapshot") {
29+
rawURL, err := session.GetSnapshotURL(deviceID)
30+
if err != nil {
31+
return nil, err
32+
}
33+
rawURL += "/current.jpg?" + query.Get("snapshot") + "#header=Cookie:" + session.GetCookieString(rawURL)
34+
return streams.GetProducer(rawURL)
35+
}
36+
37+
room, err := session.WebrtcCreateRoom(deviceID)
38+
if err != nil {
39+
return nil, err
40+
}
41+
42+
return goloomClient(room.ServiceUrl, room.ServiceName, room.RoomId, room.ParticipantId, room.Credentials)
43+
})
44+
}

main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import (
3939
"github.com/AlexxIT/go2rtc/internal/webrtc"
4040
"github.com/AlexxIT/go2rtc/internal/webtorrent"
4141
"github.com/AlexxIT/go2rtc/internal/wyoming"
42+
"github.com/AlexxIT/go2rtc/internal/yandex"
4243
"github.com/AlexxIT/go2rtc/pkg/shell"
4344
)
4445

@@ -96,6 +97,7 @@ func main() {
9697
alsa.Init() // alsa source
9798
flussonic.Init()
9899
eseecloud.Init()
100+
yandex.Init()
99101

100102
// 6. Helper modules
101103

pkg/webrtc/server.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ transeivers:
6565

6666
switch tr.Direction() {
6767
case webrtc.RTPTransceiverDirectionSendrecv:
68-
_ = tr.Sender().Stop()
68+
_ = tr.Sender().Stop() // don't know if necessary
69+
_ = tr.SetSender(tr.Sender(), nil) // set direction to recvonly
6970
case webrtc.RTPTransceiverDirectionSendonly:
7071
_ = tr.Stop()
7172
}

0 commit comments

Comments
 (0)