Compare commits

..

22 Commits

Author SHA1 Message Date
Alexey Khit
f60b55b6fa Update version to 1.1.2 2023-02-09 07:48:28 +03:00
Alexey Khit
c42413866d Remove RTSP wrong channel ID from logs 2023-02-09 07:48:11 +03:00
Alexey Khit
b137eb66d0 Fix more sizes for RTSP MJPEG #83 2023-02-09 07:18:58 +03:00
Alexey Khit
6a40039645 Fix MJPEG processing for wallpanel project #248 2023-02-09 07:18:58 +03:00
Alexey Khit
2e4b28d871 Fix RTSP auth for RtspServer project #244 2023-02-09 07:18:58 +03:00
Alexey Khit
58146b7e7e Fix H265 processing for RtspServer project #244 2023-02-09 07:18:58 +03:00
Alexey Khit
23db40220b Fix H264 processing for RtspServer project #244 2023-02-09 07:18:58 +03:00
Alex X
557aac185d Merge pull request #220 from skrashevich/macos-lipo
Generate universal macOS binary on release
2023-02-09 07:18:06 +03:00
Alexey Khit
9ed4d4cedb Fix parsing SDP from Reolink Doorbell 2023-02-07 20:02:23 +03:00
Alexey Khit
b05cbdf3d3 Make GetProfileLevelID func more smarter 2023-02-06 20:53:55 +03:00
Alexey Khit
497594f53f Fix buggy SDP parsing 2023-02-06 11:46:00 +03:00
Alexey Khit
73cdb39335 Add camera experience section to readme 2023-02-06 09:32:01 +03:00
Sergey Krashevich
a388002b12 Merge branch 'master' into macos-lipo 2023-02-04 20:40:46 +03:00
Alexey Khit
6d1c0a2459 Fix SDP parsing from cheap Chinese cameras 2023-02-04 10:01:35 +03:00
Alexey Khit
da3137b6f0 Add User-Agent to RTSP Describe #235 2023-02-03 14:11:30 +03:00
Alexey Khit
d21ce3d27d Jump over wrong packets from RTSP 2023-02-03 12:54:49 +03:00
Alexey Khit
8cee4179f2 Fix another buggy Chinese cameras 2023-02-03 12:54:49 +03:00
Alexey Khit
1153ee3652 Fix support WebRTC for Chromecast 1 2023-02-03 11:41:51 +03:00
Alexey Khit
3240301f27 Fix autofullscreen with MP4 for iPhones 2023-02-03 11:41:43 +03:00
Alexey Khit
2a20251dbd Fix autoplay after background 2023-02-03 11:41:37 +03:00
Alexey Khit
5a2d7de56b Add projects using go2rtc section to readme 2023-02-03 11:38:26 +03:00
Sergey Krashevich
b7391f58a5 Update release.yml 2023-01-28 03:21:03 +03:00
19 changed files with 333 additions and 72 deletions

View File

@@ -15,13 +15,19 @@ jobs:
- name: Generate changelog
run: |
echo -e "$(git log $(git describe --tags --abbrev=0)..HEAD --oneline | awk '{print "- "$0}')" > CHANGELOG.md
- name: install lipo
run: |
curl -L -o /tmp/lipo https://github.com/konoui/lipo/releases/latest/download/lipo_Linux_amd64
chmod +x /tmp/lipo
mv /tmp/lipo /usr/local/bin
- name: Build Go binaries
run: |
#!/bin/bash
esport CGO_ENABLED=0
export CGO_ENABLED=0
mkdir artifacts
mkdir -p artifacts
export GOOS=windows
export GOARCH=amd64
export FILENAME=artifacts/go2rtc_win64.zip
@@ -65,13 +71,14 @@ jobs:
export GOOS=darwin
export GOARCH=amd64
export FILENAME=artifacts/go2rtc_mac_amd64.zip
go build -ldflags "-s -w" -trimpath && 7z a -mx9 -sdel "$FILENAME" go2rtc
go build -ldflags "-s -w" -trimpath -o go2rtc.amd64
export GOOS=darwin
export GOARCH=arm64
export FILENAME=artifacts/go2rtc_mac_arm64.zip
go build -ldflags "-s -w" -trimpath && 7z a -mx9 -sdel "$FILENAME" go2rtc
go build -ldflags "-s -w" -trimpath -o go2rtc.arm64
export FILENAME=artifacts/go2rtc_mac_universal.zip
lipo -output go2rtc -create go2rtc.arm64 go2rtc.amd64 && 7z a -mx9 -sdel "$FILENAME" go2rtc
parallel --jobs $(nproc) "upx {}" ::: artifacts/go2rtc_linux_*
- name: Setup tmate session

View File

@@ -59,6 +59,8 @@ Ultimate camera streaming application with support RTSP, WebRTC, HomeKit, FFmpeg
* [Codecs filters](#codecs-filters)
* [Codecs madness](#codecs-madness)
* [Codecs negotiation](#codecs-negotiation)
* [Projects using go2rtc](#projects-using-go2rtc)
* [Camera experience](#cameras-experience)
* [TIPS](#tips)
* [FAQ](#faq)
@@ -761,6 +763,21 @@ streams:
**PS.** You can select `PCMU` or `PCMA` codec in camera setting and don't use transcoding at all. Or you can select `AAC` codec for main stream and `PCMU` codec for second stream and add both RTSP to YAML config, this also will work fine.
## Projects using go2rtc
- [Frigate 12+](https://frigate.video/) - open source NVR built around real-time AI object detection
- [ring-mqtt](https://github.com/tsightler/ring-mqtt) - Ring devices to MQTT Bridge
- [EufyP2PStream](https://github.com/oischinger/eufyp2pstream) - A small project that provides a Video/Audio Stream from Eufy cameras that don't directly support RTSP
## Cameras experience
- [Dahua](https://www.dahuasecurity.com/) - reference implementation streaming protocols, a lot of settings, high stream quality, multiple streaming clients
- [Hikvision](https://www.hikvision.com/) - a lot of proprietary streaming technologies
- [Reolink](https://reolink.com/) - some models has awful unusable RTSP realisation and not best HTTP-FLV alternative (I recommend that you contact Reolink support for new firmware), few settings
- [Sonoff](https://sonoff.tech/) - very low stream quality, no settings, not best protocol implementation
- [TP-Link](https://www.tp-link.com/) - few streaming clients, packet loss?
- Chinese cheap noname cameras, Wyze Cams, Xiaomi cameras with hacks (usual has `/live/ch00_1` in RTSP URL) - awful but usable RTSP protocol realisation, low stream quality, few settings, packet loss?
## TIPS
**Using apps for low RTSP delay**

View File

@@ -14,7 +14,7 @@ import (
"time"
)
var Version = "1.1.1"
var Version = "1.1.2"
var UserAgent = "go2rtc/" + Version
var ConfigPath string

View File

@@ -112,6 +112,8 @@ func rtspHandler(url string) (streamer.Producer, error) {
log.Trace().Msgf("[rtsp] client request:\n%s", msg)
case *tcp.Response:
log.Trace().Msgf("[rtsp] client response:\n%s", msg)
case string:
log.Trace().Msgf("[rtsp] client msg: %s", msg)
}
})
}

View File

@@ -3,6 +3,7 @@ package h264
import (
"encoding/base64"
"encoding/binary"
"encoding/hex"
"fmt"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"strings"
@@ -48,21 +49,39 @@ func Join(ps, iframe []byte) []byte {
return b
}
// GetProfileLevelID - get profile from fmtp line
// Some devices won't play video with high level, so limit max profile and max level.
// And return some profile even if fmtp line is empty.
func GetProfileLevelID(fmtp string) string {
if fmtp == "" {
return ""
}
// avc1.640029 - H.264 high 4.1 (Chromecast 1st and 2nd Gen)
profile := byte(0x64)
capab := byte(0)
level := byte(0x29)
// some cameras has wrong profile-level-id
// https://github.com/AlexxIT/go2rtc/issues/155
if s := streamer.Between(fmtp, "sprop-parameter-sets=", ","); s != "" {
sps, _ := base64.StdEncoding.DecodeString(s)
if len(sps) >= 4 {
return fmt.Sprintf("%06X", sps[1:4])
if fmtp != "" {
var conf []byte
// some cameras has wrong profile-level-id
// https://github.com/AlexxIT/go2rtc/issues/155
if s := streamer.Between(fmtp, "sprop-parameter-sets=", ","); s != "" {
if sps, _ := base64.StdEncoding.DecodeString(s); len(sps) >= 4 {
conf = sps[1:4]
}
} else if s = streamer.Between(fmtp, "profile-level-id=", ";"); s != "" {
conf, _ = hex.DecodeString(s)
}
if conf != nil {
if conf[0] < profile {
profile = conf[0]
capab = conf[1]
}
if conf[2] < level {
level = conf[2]
}
}
}
return streamer.Between(fmtp, "profile-level-id=", ";")
return fmt.Sprintf("%02X%02X%02X", profile, capab, level)
}
func GetParameterSet(fmtp string) (sps, pps []byte) {

View File

@@ -10,6 +10,8 @@ import (
const RTPPacketVersionAVC = 0
const PSMaxSize = 128 // the biggest SPS I've seen is 48 (EZVIZ CS-CV210)
func RTPDepay(track *streamer.Track) streamer.WrapperFunc {
depack := &codecs.H264Packet{IsAVC: true}
@@ -29,11 +31,15 @@ func RTPDepay(track *streamer.Track) streamer.WrapperFunc {
// Fix TP-Link Tapo TC70: sends SPS and PPS with packet.Marker = true
// Reolink Duo 2: sends SPS with Marker and PPS without
if packet.Marker && len(payload) < 128 {
if packet.Marker && len(payload) < PSMaxSize {
switch NALUType(payload) {
case NALUTypeSPS, NALUTypePPS:
buf = append(buf, payload...)
return nil
case NALUTypeSEI:
// RtspServer https://github.com/AlexxIT/go2rtc/issues/244
// sends, marked SPS, marked PPS, marked SEI, marked IFrame
return nil
}
}
@@ -70,7 +76,10 @@ func RTPDepay(track *streamer.Track) streamer.WrapperFunc {
if len(buf) > 0 {
payload = append(buf, payload...)
buf = buf[:0]
} else {
}
// should not be that huge SPS
if NALUType(payload) == NALUTypeSPS && binary.BigEndian.Uint32(payload) >= PSMaxSize {
// some Chinese buggy cameras has single packet with SPS+PPS+IFrame separated by 00 00 00 01
// https://github.com/AlexxIT/WebRTC/issues/391
// https://github.com/AlexxIT/WebRTC/issues/392

View File

@@ -7,14 +7,16 @@ import (
)
const (
NALUTypePFrame = 1
NALUTypeIFrame = 19
NALUTypeIFrame2 = 20
NALUTypeIFrame3 = 21
NALUTypeVPS = 32
NALUTypeSPS = 33
NALUTypePPS = 34
NALUTypeFU = 49
NALUTypePFrame = 1
NALUTypeIFrame = 19
NALUTypeIFrame2 = 20
NALUTypeIFrame3 = 21
NALUTypeVPS = 32
NALUTypeSPS = 33
NALUTypePPS = 34
NALUTypePrefixSEI = 39
NALUTypeSuffixSEI = 40
NALUTypeFU = 49
)
func NALUType(b []byte) byte {

View File

@@ -20,6 +20,16 @@ func RTPDepay(track *streamer.Track) streamer.WrapperFunc {
nuType := (data[0] >> 1) & 0x3F
//log.Printf("[RTP] codec: %s, nalu: %2d, size: %6d, ts: %10d, pt: %2d, ssrc: %d, seq: %d, %v", track.Codec.Name, nuType, len(packet.Payload), packet.Timestamp, packet.PayloadType, packet.SSRC, packet.SequenceNumber, packet.Marker)
// Fix for RtspServer https://github.com/AlexxIT/go2rtc/issues/244
if packet.Marker && len(data) < h264.PSMaxSize {
switch nuType {
case NALUTypeVPS, NALUTypeSPS, NALUTypePPS:
packet.Marker = false
case NALUTypePrefixSEI, NALUTypeSuffixSEI:
return nil
}
}
if nuType == NALUTypeFU {
switch data[2] >> 6 {
case 2: // begin

View File

@@ -122,7 +122,11 @@ func (c *Client) startJPEG() error {
}
func (c *Client) startMJPEG(boundary string) error {
boundary = "--" + boundary
// some cameras add prefix to boundary header:
// https://github.com/TheTimeWalker/wallpanel-android
if !strings.HasPrefix(boundary, "--") {
boundary = "--" + boundary
}
r := bufio.NewReader(c.res.Body)
tp := textproto.NewReader(r)

View File

@@ -43,9 +43,18 @@ func RTPDepay(track *streamer.Track) streamer.WrapperFunc {
w := uint16(packet.Payload[6]) << 3
h := uint16(packet.Payload[7]) << 3
// fix 2560x1920 and 2560x1440
if w == 512 && (h == 1920 || h == 1440) {
// fix sizes more than 2040
switch {
// 512x1920 512x1440
case w == cutSize(2560) && (h == 1920 || h == 1440):
w = 2560
// 1792x112
case w == cutSize(3840) && h == cutSize(2160):
w = 3840
h = 2160
// 256x1296
case w == cutSize(2304) && h == 1296:
w = 2304
}
//fmt.Printf("t: %d, q: %d, w: %d, h: %d\n", t, q, w, h)
@@ -81,6 +90,10 @@ func RTPPay() streamer.WrapperFunc {
}
}
func cutSize(size uint16) uint16 {
return ((size >> 3) & 0xFF) << 3
}
//func RTPPay() streamer.WrapperFunc {
// const packetSize = 1436
//

View File

@@ -304,6 +304,12 @@ func (c *Conn) Describe() error {
req.Header.Set("Require", "www.onvif.org/ver20/backchannel")
}
if c.UserAgent != "" {
// this camera will answer with 401 on DESCRIBE without User-Agent
// https://github.com/AlexxIT/go2rtc/issues/235
req.Header.Set("User-Agent", c.UserAgent)
}
res, err := c.Do(req)
if err != nil {
return err
@@ -743,6 +749,9 @@ func (c *Conn) Handle() (err error) {
return
}
var channelID byte
var size uint16
if buf4[0] != '$' {
switch string(buf4) {
case "RTSP":
@@ -751,26 +760,62 @@ func (c *Conn) Handle() (err error) {
return
}
c.Fire(res)
continue
case "OPTI", "TEAR", "DESC", "SETU", "PLAY", "PAUS", "RECO", "ANNO", "GET_", "SET_":
var req *tcp.Request
if req, err = tcp.ReadRequest(c.reader); err != nil {
return
}
c.Fire(req)
continue
default:
return fmt.Errorf("RTSP wrong input")
for i := 0; ; i++ {
// search next start symbol
if _, err = c.reader.ReadBytes('$'); err != nil {
return err
}
if channelID, err = c.reader.ReadByte(); err != nil {
return err
}
// check if channel ID exists
if c.channels[channelID] == nil {
continue
}
buf4 = make([]byte, 2)
if _, err = io.ReadFull(c.reader, buf4); err != nil {
return err
}
// check if size good for RTP
size = binary.BigEndian.Uint16(buf4)
if size <= 1500 {
break
}
// 10 tries to find good packet
if i >= 10 {
return fmt.Errorf("RTSP wrong input")
}
}
c.Fire("RTSP wrong input")
}
continue
}
} else {
// hope that the odd channels are always RTCP
channelID = buf4[1]
// hope that the odd channels are always RTCP
channelID := buf4[1]
// get data size
size = binary.BigEndian.Uint16(buf4[2:])
// get data size
size := int(binary.BigEndian.Uint16(buf4[2:]))
if _, err = c.reader.Discard(4); err != nil {
return
// skip 4 bytes from c.reader.Peek
if _, err = c.reader.Discard(4); err != nil {
return
}
}
// init memory for data
@@ -779,7 +824,7 @@ func (c *Conn) Handle() (err error) {
return
}
c.receive += size
c.receive += int(size)
if channelID&1 == 0 {
packet := &rtp.Packet{}
@@ -790,10 +835,8 @@ func (c *Conn) Handle() (err error) {
track := c.channels[channelID]
if track != nil {
_ = track.WriteRTP(packet)
//return fmt.Errorf("wrong channelID: %d", channelID)
} else {
continue // TODO: maybe fix this
//panic("wrong channelID")
//c.Fire("wrong channelID: " + strconv.Itoa(int(channelID)))
}
} else {
msg := &RTCP{Channel: channelID}

View File

@@ -4,7 +4,10 @@ import (
"bytes"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/pion/rtcp"
"github.com/pion/sdp/v3"
"net/url"
"regexp"
"strconv"
"strings"
)
@@ -20,22 +23,43 @@ s=-
t=0 0`
func UnmarshalSDP(rawSDP []byte) ([]*streamer.Media, error) {
medias, err := streamer.UnmarshalSDP(rawSDP)
if err != nil {
// fix bug from Reolink Doorbell
if i := bytes.Index(rawSDP, []byte("a=sendonlym=")); i > 0 {
rawSDP = append(rawSDP[:i+11], rawSDP[i+10:]...)
rawSDP[i+10] = '\n'
}
sd := &sdp.SessionDescription{}
if err := sd.Unmarshal(rawSDP); err != nil {
// fix multiple `s=` https://github.com/AlexxIT/WebRTC/issues/417
re, _ := regexp.Compile("\ns=[^\n]+")
rawSDP = re.ReplaceAll(rawSDP, nil)
// fix SDP header for some cameras
i := bytes.Index(rawSDP, []byte("\nm="))
if i > 0 {
if i := bytes.Index(rawSDP, []byte("\nm=")); i > 0 {
rawSDP = append([]byte(sdpHeader), rawSDP[i:]...)
medias, err = streamer.UnmarshalSDP(rawSDP)
sd = &sdp.SessionDescription{}
err = sd.Unmarshal(rawSDP)
}
if err != nil {
return nil, err
}
}
// fix bug in ONVIF spec
// https://www.onvif.org/specs/stream/ONVIF-Streaming-Spec-v241.pdf
medias := streamer.UnmarshalMedias(sd.MediaDescriptions)
for _, media := range medias {
// Check buggy SDP with fmtp for H264 on another track
// https://github.com/AlexxIT/WebRTC/issues/419
for _, codec := range media.Codecs {
if codec.Name == streamer.CodecH264 && codec.FmtpLine == "" {
codec.FmtpLine = findFmtpLine(codec.PayloadType, sd.MediaDescriptions)
}
}
// fix bug in ONVIF spec
// https://www.onvif.org/specs/stream/ONVIF-Streaming-Spec-v241.pdf
switch media.Direction {
case streamer.DirectionRecvonly, "":
media.Direction = streamer.DirectionSendonly
@@ -47,6 +71,17 @@ func UnmarshalSDP(rawSDP []byte) ([]*streamer.Media, error) {
return medias, nil
}
func findFmtpLine(payloadType uint8, descriptions []*sdp.MediaDescription) string {
s := strconv.Itoa(int(payloadType))
for _, md := range descriptions {
codec := streamer.UnmarshalCodec(md, s)
if codec.FmtpLine != "" {
return codec.FmtpLine
}
}
return ""
}
// urlParse fix bugs:
// 1. Content-Base: rtsp://::ffff:192.168.1.123/onvif/profile.1/
// 2. Content-Base: rtsp://rtsp://turret2-cam.lan:554/stream1/

View File

@@ -18,3 +18,91 @@ func TestURLParse(t *testing.T) {
assert.Empty(t, err)
assert.Equal(t, "turret2-cam.lan:554", u.Host)
}
func TestBugSDP1(t *testing.T) {
// https://github.com/AlexxIT/WebRTC/issues/417
s := `v=0
o=- 91674849066 1 IN IP4 192.168.1.123
s=RtspServer
i=live
t=0 0
a=control:*
a=range:npt=0-
m=video 0 RTP/AVP 96
c=IN IP4 0.0.0.0
s=RtspServer
i=live
a=control:track0
a=range:npt=0-
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1;profile-level-id=42001E;sprop-parameter-sets=Z0IAHvQCgC3I,aM48gA==
a=control:track0
m=audio 0 RTP/AVP 97
c=IN IP4 0.0.0.0
s=RtspServer
i=live
a=control:track1
a=range:npt=0-
a=rtpmap:97 MPEG4-GENERIC/8000/1
a=fmtp:97 profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=1588
a=control:track1
`
medias, err := UnmarshalSDP([]byte(s))
assert.Nil(t, err)
assert.NotNil(t, medias)
}
func TestBugSDP2(t *testing.T) {
// https://github.com/AlexxIT/WebRTC/issues/419
s := `v=0
o=- 1675628282 1675628283 IN IP4 192.168.1.123
s=streamed by the RTSP server
t=0 0
m=video 0 RTP/AVP 96
a=rtpmap:96 H264/90000
a=control:track0
m=audio 0 RTP/AVP 8
a=rtpmap:0 pcma/8000/1
a=control:track1
a=framerate:25
a=range:npt=now-
a=fmtp:96 packetization-mode=1;profile-level-id=64001F;sprop-parameter-sets=Z0IAH5WoFAFuQA==,aM48gA==
`
medias, err := UnmarshalSDP([]byte(s))
assert.Nil(t, err)
assert.NotNil(t, medias)
assert.NotEqual(t, "", medias[0].Codecs[0].FmtpLine)
}
func TestBugSDP3(t *testing.T) {
s := `v=0
o=- 1675775048103026 1 IN IP4 192.168.1.123
s=Session streamed by "preview"
t=0 0
a=tool:LIVE555 Streaming Media v2020.08.12
a=type:broadcast
a=control:*
a=range:npt=0-
a=x-qt-text-nam:Session streamed by "preview"
m=video 0 RTP/AVP 96
c=IN IP4 0.0.0.0
b=AS:8192
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1;profile-level-id=640033;sprop-parameter-sets=Z2QAM6wVFKAoAPGQ,aO48sA==
a=recvonly
a=control:track1
m=audio 0 RTP/AVP 8
a=control:track2
a=rtpmap:8 PCMA/8000
a=sendonlym=audio 0 RTP/AVP 98
c=IN IP4 0.0.0.0
b=AS:8192
a=rtpmap:98 MPEG4-GENERIC/16000
a=fmtp:98 streamtype=5;profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=1408;
a=recvonly
a=control:track3
`
medias, err := UnmarshalSDP([]byte(s))
assert.Nil(t, err)
assert.Len(t, medias, 3)
}

View File

@@ -166,14 +166,8 @@ func (c *Codec) Match(codec *Codec) bool {
(c.Channels == codec.Channels || codec.Channels == 0)
}
func UnmarshalSDP(rawSDP []byte) ([]*Media, error) {
sd := &sdp.SessionDescription{}
if err := sd.Unmarshal(rawSDP); err != nil {
return nil, err
}
var medias []*Media
for _, md := range sd.MediaDescriptions {
func UnmarshalMedias(descriptions []*sdp.MediaDescription) (medias []*Media) {
for _, md := range descriptions {
media := UnmarshalMedia(md)
if media.Direction == DirectionSendRecv {
@@ -187,7 +181,7 @@ func UnmarshalSDP(rawSDP []byte) ([]*Media, error) {
medias = append(medias, media)
}
return medias, nil
return
}
func MarshalSDP(name string, medias []*Media) ([]byte, error) {

View File

@@ -70,7 +70,9 @@ func (a *Auth) Write(req *Request) {
case AuthBasic:
req.Header.Set("Authorization", a.header)
case AuthDigest:
uri := req.URL.RequestURI()
// important to use String except RequestURL for RtspServer:
// https://github.com/AlexxIT/go2rtc/issues/244
uri := req.URL.String()
h2 := HexMD5(req.Method, uri)
response := HexMD5(a.h1nonce, h2)
header := a.header + fmt.Sprintf(

View File

@@ -66,13 +66,7 @@ func (c *Consumer) AddTrack(media *streamer.Media, track *streamer.Track) *strea
c.mimeType += ","
}
// TODO: fixme
// some devices won't play high level
if stream.RecordInfo.AVCLevelIndication <= 0x29 {
c.mimeType += "avc1." + h264.GetProfileLevelID(codec.FmtpLine)
} else {
c.mimeType += "avc1.640029"
}
c.mimeType += "avc1." + h264.GetProfileLevelID(codec.FmtpLine)
c.streams = append(c.streams, stream)

View File

@@ -2,6 +2,7 @@ package webrtc
import (
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/pion/sdp/v3"
"github.com/pion/webrtc/v3"
)
@@ -90,12 +91,15 @@ func (c *Conn) SetOffer(offer string) (err error) {
if err = c.Conn.SetRemoteDescription(sdOffer); err != nil {
return
}
rawSDP := []byte(c.Conn.RemoteDescription().SDP)
medias, err := streamer.UnmarshalSDP(rawSDP)
if err != nil {
sd := &sdp.SessionDescription{}
if err = sd.Unmarshal(rawSDP); err != nil {
return
}
medias := streamer.UnmarshalMedias(sd.MediaDescriptions)
// sort medias, so video will always be before audio
// and ignore application media from Hass default lovelace card
for _, media := range medias {

View File

@@ -48,6 +48,18 @@ pc.ontrack = ev => {
}
```
## Chromecast 1
2023-02-02. Error:
```
InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument.
```
User-Agent: `Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.47 Safari/537.36 CrKey/1.36.159268`
https://webrtc.org/getting-started/unified-plan-transition-guide?hl=en
## Useful links
- https://www.webrtc-experiment.com/DetectRTC/
@@ -58,3 +70,4 @@ pc.ontrack = ev => {
- https://chromium.googlesource.com/external/w3c/web-platform-tests/+/refs/heads/master/media-source/mediasource-is-type-supported.html
- https://googlechrome.github.io/samples/media/sourcebuffer-changetype.html
- https://chromestatus.com/feature/5100845653819392
- https://developer.apple.com/documentation/webkit/delivering_video_content_for_safari

View File

@@ -11,6 +11,7 @@
* - MediaSource for Safari iOS all
* - Customized built-in elements (extends HTMLVideoElement) because all Safari
* - Public class fields because old Safari (before 14.0)
* - Autoplay for Safari
*/
export class VideoRTC extends HTMLElement {
constructor() {
@@ -60,7 +61,10 @@ export class VideoRTC extends HTMLElement {
* [config] WebRTC configuration
* @type {RTCConfiguration}
*/
this.pcConfig = {iceServers: [{urls: "stun:stun.l.google.com:19302"}]};
this.pcConfig = {
iceServers: [{urls: 'stun:stun.l.google.com:19302'}],
sdpSemantics: 'unified-plan', // important for Chromecast 1
};
/**
* [info] WebSocket connection state. Values: CONNECTING, OPEN, CLOSED
@@ -189,8 +193,8 @@ export class VideoRTC extends HTMLElement {
const seek = this.video.seekable;
if (seek.length > 0) {
this.video.currentTime = seek.end(seek.length - 1);
this.play();
}
this.play();
} else {
this.oninit();
}
@@ -558,6 +562,7 @@ export class VideoRTC extends HTMLElement {
/** @type {HTMLVideoElement} */
const video2 = document.createElement("video");
video2.autoplay = true;
video2.playsInline = true;
video2.muted = true;
video2.addEventListener("loadeddata", ev => {