Skip to content

Commit

Permalink
Add audio encryption/decryption utilities (#573)
Browse files Browse the repository at this point in the history
* Add audio encryption/decryption utilities

* Fix comment typo

Co-authored-by: lukasIO <[email protected]>

---------

Co-authored-by: lukasIO <[email protected]>
  • Loading branch information
skheyfets-asapp and lukasIO authored Dec 13, 2024
1 parent 7610e16 commit 5963330
Show file tree
Hide file tree
Showing 7 changed files with 324 additions and 10 deletions.
204 changes: 204 additions & 0 deletions encryption.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
package lksdk

import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"errors"
"io"

"golang.org/x/crypto/hkdf"
"golang.org/x/crypto/pbkdf2"
)

const (
LIVEKIT_SDK_SALT = "LKFrameEncryptionKey"
LIVEKIT_IV_LENGTH = 12
LIVEKIT_PBKDF_ITERATIONS = 100000
LIVEKIT_KEY_SIZE_BYTES = 16
LIVEKIT_HKDF_INFO_BYTES = 128
unencrypted_audio_bytes = 1
)

var ErrIncorrectKeyLength = errors.New("incorrect key length for encryption/decryption")
var ErrUnableGenerateIV = errors.New("unable to generate iv for encryption")
var ErrIncorrectIVLength = errors.New("incorrect iv length")
var ErrIncorrectSecretLength = errors.New("input secret provided to derivation function cannot be empty or nil")
var ErrIncorrectSaltLength = errors.New("input salt provided to derivation function cannot be empty or nil")

func DeriveKeyFromString(password string) ([]byte, error) {
return DeriveKeyFromStringCustomSalt(password, LIVEKIT_SDK_SALT)
}

func DeriveKeyFromStringCustomSalt(password, salt string) ([]byte, error) {

if password == "" {
return nil, ErrIncorrectSecretLength
}
if salt == "" {
return nil, ErrIncorrectSaltLength
}

encPassword := []byte(password)
encSalt := []byte(salt)

return pbkdf2.Key(encPassword, encSalt, LIVEKIT_PBKDF_ITERATIONS, LIVEKIT_KEY_SIZE_BYTES, sha256.New), nil

}

func DeriveKeyFromBytes(secret []byte) ([]byte, error) {
return DeriveKeyFromBytesCustomSalt(secret, LIVEKIT_SDK_SALT)
}

func DeriveKeyFromBytesCustomSalt(secret []byte, salt string) ([]byte, error) {

info := make([]byte, LIVEKIT_HKDF_INFO_BYTES)
encSalt := []byte(salt)

if secret == nil {
return nil, ErrIncorrectSecretLength
}
if salt == "" {
return nil, ErrIncorrectSaltLength
}

hkdfReader := hkdf.New(sha256.New, secret, encSalt, info)

key := make([]byte, LIVEKIT_KEY_SIZE_BYTES)
_, err := io.ReadFull(hkdfReader, key)
if err != nil {
return nil, err
}

return key, nil

}

// Take audio sample (body of RTP) encrypted by LiveKit client SDK, extract IV and decrypt using provided key
// Encrypted sample format based on livekit client sdk
// ---------+-------------------------+---------+----
// payload |IV...(length = IV_LENGTH)|IV_LENGTH|KID|
// ---------+-------------------------+---------+----
// First byte of audio frame is not encrypted and only authenticated
// payload - variable bytes
// IV - variable bytes (equal to IV_LENGTH bytes)
// IV_LENGTH - 1 byte
// KID (Key ID) - 1 byte - ignored here, key is provided as parameter to function
func DecryptGCMAudioSample(sample, key, sifTrailer []byte) ([]byte, error) {

if len(key) != 16 {
return nil, ErrIncorrectKeyLength
}

if sifTrailer != nil && len(sample) >= len(sifTrailer) {
possibleTrailer := sample[len(sample)-len(sifTrailer):]
if bytes.Equal(possibleTrailer, sifTrailer) {
// this is unencrypted Server Injected Frame (SIF) that should be dropped
return nil, nil
}

}

// variable naming is kept close to LiveKit client SDK decrypt function
// https://github.com/livekit/client-sdk-js/blob/main/src/e2ee/worker/FrameCryptor.ts#L402

frameHeader := sample[:unencrypted_audio_bytes] // first unencrypted bytes are "frameHeader" and used for authentication later
frameTrailer := sample[len(sample)-2:] // last 2 bytes having IV_LENGTH and KID (1 byte each)
ivLength := int(frameTrailer[0]) // single byte, Endianness doesn't matter
ivStart := len(sample) - len(frameTrailer) - ivLength
if ivStart < 0 {
return nil, ErrIncorrectIVLength
}

iv := make([]byte, ivLength)
copy(iv, sample[ivStart:ivStart+ivLength]) // copy IV value out of sample into iv

cipherTextStart := len(frameHeader)
cipherTextLength := len(sample) - len(frameTrailer) - ivLength - len(frameHeader)
cipherText := make([]byte, cipherTextLength)
copy(cipherText, sample[cipherTextStart:cipherTextStart+cipherTextLength])

// setup AES
aesCipher, err := aes.NewCipher(key)
if err != nil {
return nil, err
}

aesGCM, err := cipher.NewGCMWithNonceSize(aesCipher, ivLength) // standard Nonce size is 12 bytes, but since it MAY be different in the sample, we use the one from the sample
if err != nil {
return nil, err
}

// fmt.Println("**** DECRYPTION BEGIN ********")
plainText, err := aesGCM.Open(nil, iv, cipherText, frameHeader)
if err != nil {
return nil, err
}

newData := make([]byte, len(frameHeader)+len(plainText)) // allocate space for final packet

_ = copy(newData[0:], frameHeader) // put unencrypted frameHeader first
_ = copy(newData[len(frameHeader):], plainText) // add decrypted remaining value

return newData, nil

}

// Take audio sample (body of RTP) and encrypts it using AES-GCM 128bit with provided key
// Encrypted sample format based on livekit client sdk
// ---------+-------------------------+---------+----
// payload |IV...(length = IV_LENGTH)|IV_LENGTH|KID|
// ---------+-------------------------+---------+----
// First byte of audio frame is not encrypted and only authenticated
// payload - variable bytes
// IV - variable bytes (equal to IV_LENGTH bytes) - 12 random bytes
// IV_LENGTH - 1 byte - 12 bytes fixed
// KID (Key ID) - 1 byte - taken from "kid" parameter
func EncryptGCMAudioSample(sample, key []byte, kid uint8) ([]byte, error) {

if len(key) != 16 {
return nil, ErrIncorrectKeyLength
}

// variable naming is kept close to LiveKit client SDK decrypt function
// https://github.com/livekit/client-sdk-js/blob/main/src/e2ee/worker/FrameCryptor.ts#L402

frameHeader := append(make([]byte, 0), sample[:unencrypted_audio_bytes]...) // first unencrypted bytes are "frameHeader" and used for authentication later
iv := make([]byte, LIVEKIT_IV_LENGTH)
_, err := rand.Read(iv)
if err != nil {
return nil, errors.Join(ErrUnableGenerateIV, err)
}

frameTrailer := []byte{LIVEKIT_IV_LENGTH, kid} // last 2 bytes having IV_LENGTH and KID (1 byte each)

plainTextStart := len(frameHeader)
plainTextLength := len(sample) - len(frameHeader)
plainText := make([]byte, plainTextLength)
copy(plainText, sample[plainTextStart:plainTextStart+plainTextLength])

// setup AES
aesCipher, err := aes.NewCipher(key)
if err != nil {
return nil, err
}

aesGCM, err := cipher.NewGCMWithNonceSize(aesCipher, LIVEKIT_IV_LENGTH) // standard Nonce size is 12 bytes, but using one from defined constant (which matches Javascript SDK)
if err != nil {
return nil, err
}

cipherText := aesGCM.Seal(nil, iv, plainText, frameHeader)

newData := make([]byte, len(frameHeader)+len(cipherText)+len(iv)+len(frameTrailer)) // allocate space for final packet

_ = copy(newData[0:], frameHeader) // put unencrypted frameHeader first
_ = copy(newData[len(frameHeader):], cipherText) // add cipherText
_ = copy(newData[len(frameHeader)+len(cipherText):], iv) // add iv
_ = copy(newData[len(frameHeader)+len(cipherText)+len(iv):], frameTrailer) // add trailer

return newData, nil

}
80 changes: 80 additions & 0 deletions encryption_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package lksdk

import (
"testing"

"github.com/stretchr/testify/assert"
)

var opusEncryptedFrame = []byte{120, 145, 24, 159, 76, 65, 130, 48, 144, 249, 17, 112, 134, 78, 250, 129, 171, 194, 16, 173, 73, 196, 5, 152, 69, 225, 28, 210, 196, 241, 226, 139, 231, 172, 51, 38, 139, 179, 245, 182, 170, 8, 122, 117, 98, 144, 123, 95, 73, 89, 119, 39, 205, 20, 191, 55, 121, 59, 239, 192, 85, 224, 228, 143, 10, 113, 195, 223, 118, 42, 2, 32, 22, 17, 77, 227, 109, 160, 245, 202, 189, 63, 162, 164, 5, 241, 24, 151, 45, 42, 165, 131, 171, 243, 141, 53, 35, 131, 141, 52, 253, 188, 12, 0}
var opusDecryptedFrame = []byte{120, 11, 109, 82, 113, 132, 189, 156, 220, 173, 30, 109, 87, 54, 173, 99, 26, 126, 166, 37, 127, 234, 110, 211, 230, 152, 181, 235, 197, 19, 140, 230, 179, 35, 131, 132, 29, 192, 97, 247, 108, 53, 183, 214, 77, 181, 173, 206, 175, 7, 228, 145, 93, 155, 155, 142, 14, 27, 111, 64, 96, 196, 229, 189, 142, 59, 149, 169, 99, 225, 216, 85, 186, 182}
var opusSilenceFrame = []byte{0xf8, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
var sifTrailer = []byte{50, 86, 10, 220, 108, 185, 57, 211}
var testPassphrase = "12345"

func TestDeriveKeyFromString(t *testing.T) {

password := "12345"

key, err := DeriveKeyFromString(password)
expectedKey := []byte{15, 94, 198, 66, 93, 211, 116, 46, 55, 97, 232, 121, 189, 233, 224, 22}

assert.Nil(t, err)
assert.Equal(t, key, expectedKey)
}

func TestDeriveKeyFromBytes(t *testing.T) {

inputSecret := []byte{34, 21, 187, 202, 134, 204, 168, 62, 5, 105, 40, 244, 88}
expectedKey := []byte{129, 224, 93, 62, 17, 203, 99, 136, 101, 35, 149, 128, 189, 152, 251, 76}

key, err := DeriveKeyFromBytes(inputSecret)
assert.Nil(t, err)
assert.Equal(t, expectedKey, key)

}

func TestDecryptAudioSample(t *testing.T) {

key, err := DeriveKeyFromString(testPassphrase)
assert.Nil(t, err)

decryptedFrame, err := DecryptGCMAudioSample(opusEncryptedFrame, key, sifTrailer)

assert.Nil(t, err)
assert.Equal(t, opusDecryptedFrame, decryptedFrame)

var sifFrame []byte
sifFrame = append(sifFrame, opusSilenceFrame...)
sifFrame = append(sifFrame, sifTrailer...)

decryptedFrame, err = DecryptGCMAudioSample(sifFrame, key, sifTrailer)
assert.Nil(t, err)
assert.Nil(t, decryptedFrame)

}

func TestEncryptAudioSample(t *testing.T) {

key, err := DeriveKeyFromString(testPassphrase)
assert.Nil(t, err)

encryptedFrame, err := EncryptGCMAudioSample(opusDecryptedFrame, key, 0)

assert.Nil(t, err)

// IV is generated randomly so to verify we decrypt and make sure that we got the expected plain text frame
decryptedFrame, err := DecryptGCMAudioSample(encryptedFrame, key, sifTrailer)
assert.Nil(t, err)
assert.Equal(t, opusDecryptedFrame, decryptedFrame)

}
9 changes: 5 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ require (
github.com/stretchr/testify v1.10.0
github.com/twitchtv/twirp v8.1.3+incompatible
go.uber.org/atomic v1.11.0
golang.org/x/crypto v0.30.0
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f
google.golang.org/protobuf v1.35.2
)
Expand Down Expand Up @@ -68,11 +69,11 @@ require (
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect
golang.org/x/crypto v0.29.0 // indirect
golang.org/x/net v0.31.0 // indirect
golang.org/x/sync v0.9.0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/text v0.20.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/crypto v0.29.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/grpc v1.68.0 // indirect
Expand Down
26 changes: 20 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM=
github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4=
github.com/frostbyte73/core v0.0.12 h1:kySA8+Os6eqnPFoExD2T7cehjSAY1MRyIViL0yTy2uc=
github.com/frostbyte73/core v0.0.12/go.mod h1:XsOGqrqe/VEV7+8vJ+3a8qnCIXNbKsoEiu/czs7nrcU=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/frostbyte73/core v0.0.13 h1:W/NFPNiCkGTRzMWnCVptn6vX6Tr4a7LvN0RFc0xsC2k=
github.com/frostbyte73/core v0.0.13/go.mod h1:XsOGqrqe/VEV7+8vJ+3a8qnCIXNbKsoEiu/czs7nrcU=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
Expand All @@ -36,6 +40,10 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84=
github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI=
Expand Down Expand Up @@ -152,6 +160,8 @@ go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY=
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo=
Expand All @@ -163,13 +173,15 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand All @@ -178,8 +190,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
Expand All @@ -191,8 +203,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
Expand All @@ -204,6 +216,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0=
google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
1 change: 1 addition & 0 deletions localparticipant.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ func (p *LocalParticipant) PublishTrack(track webrtc.TrackLocal, opts *TrackPubl
DisableDtx: opts.DisableDTX,
Stereo: opts.Stereo,
Stream: opts.Stream,
Encryption: opts.Encryption,
}
if kind == TrackKindVideo {
// single layer
Expand Down
Loading

0 comments on commit 5963330

Please sign in to comment.