mirror of
https://github.com/jech/galene.git
synced 2024-11-09 18:25:58 +01:00
Label streams, not tracks.
We used to label tracks individually, in a view to using the labelling for simulcast. Since then, the WebRTC community has converged on a different strategy, where multiple tracks share a single mid and are labelled with the rid extension. We now label whole streams, which is simpler, and use the track's kind (and, in the future, the rid) to disambiguate. This changes the protocol in two ways: * in offers, the "labels" dictionary is replaced by a single "label" field; and * the syntax of the "request" message has changed.
This commit is contained in:
parent
b08a2e3943
commit
be73380f9f
7 changed files with 147 additions and 269 deletions
|
@ -65,7 +65,7 @@ You typically join a group and request media in the `onconnected` callback:
|
||||||
```javascript
|
```javascript
|
||||||
serverConnection.onconnected = function() {
|
serverConnection.onconnected = function() {
|
||||||
this.join(group, 'join', username, password);
|
this.join(group, 'join', username, password);
|
||||||
this.request('everything');
|
this.request({'':['audio','video']});
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -110,10 +110,7 @@ serverConnection.ondownstream = function(stream) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The `stream.labels` dictionary maps each track's id to one of `audio`,
|
The `stream.label` field is one of `camera`, `screenshare` or `video`.
|
||||||
`video` or `screenshare`. Since `stream.labels` is already available at
|
|
||||||
this point, you may set up an `audio` or `video` component straight away,
|
|
||||||
or you may choose to wait until the `ondowntrack` callback is called.
|
|
||||||
|
|
||||||
After a new stream is created, `ondowntrack` will be called whenever
|
After a new stream is created, `ondowntrack` will be called whenever
|
||||||
a track is added.
|
a track is added.
|
||||||
|
@ -137,10 +134,10 @@ to push a stream to the server. Given a `MediaStream` called `localStream`
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
let stream = serverConnection.newUpStream();
|
let stream = serverConnection.newUpStream();
|
||||||
|
stream.label = ...;
|
||||||
stream.onerror = ...;
|
stream.onerror = ...;
|
||||||
stream.onstatus = ...;
|
stream.onstatus = ...;
|
||||||
localStream.getTracks().forEach(t => {
|
localStream.getTracks().forEach(t => {
|
||||||
c.labels[t.id] = t.kind;
|
|
||||||
c.pc.addTrack(t, c.stream);
|
c.pc.addTrack(t, c.stream);
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
@ -150,8 +147,6 @@ the `localId` property of an existing stream, then the existing stream
|
||||||
will be closed and the server will be informed that the new stream
|
will be closed and the server will be informed that the new stream
|
||||||
replaces the existing stream.
|
replaces the existing stream.
|
||||||
|
|
||||||
See above for information about setting up the `labels` dictionary.
|
|
||||||
|
|
||||||
## Stream statistics
|
## Stream statistics
|
||||||
|
|
||||||
Some statistics about streams are made available by calling the
|
Some statistics about streams are made available by calling the
|
||||||
|
|
|
@ -135,7 +135,8 @@ A peer must explicitly request the streams that it wants to receive.
|
||||||
```
|
```
|
||||||
|
|
||||||
The field `request` is a dictionary that maps the labels of requested
|
The field `request` is a dictionary that maps the labels of requested
|
||||||
tracks to the boolean `true`.
|
streams to a list containing either 'audio', 'video' or both. An entry
|
||||||
|
with an empty key `''` serves as default.
|
||||||
|
|
||||||
## Pushing streams
|
## Pushing streams
|
||||||
|
|
||||||
|
@ -145,11 +146,11 @@ A stream is created by the sender with the `offer` message:
|
||||||
{
|
{
|
||||||
type: 'offer',
|
type: 'offer',
|
||||||
id: id,
|
id: id,
|
||||||
|
label: label,
|
||||||
replace: id,
|
replace: id,
|
||||||
source: source-id,
|
source: source-id,
|
||||||
username: username,
|
username: username,
|
||||||
sdp: sdp,
|
sdp: sdp,
|
||||||
labels: labels
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -158,15 +159,14 @@ otherwise this message creates a new stream. If the field `replace` is
|
||||||
not empty, then this request additionally requests that an existing stream
|
not empty, then this request additionally requests that an existing stream
|
||||||
with the given id should be closed, and the new stream should replace it.
|
with the given id should be closed, and the new stream should replace it.
|
||||||
|
|
||||||
|
The field `label` is one of `camera`, `screenshare` or `video`, as in the
|
||||||
|
`request` message.
|
||||||
|
|
||||||
The field `sdp` contains the raw SDP string (i.e. the `sdp` field of
|
The field `sdp` contains the raw SDP string (i.e. the `sdp` field of
|
||||||
a JSEP session description). Galène will interpret the `nack`,
|
a JSEP session description). Galène will interpret the `nack`,
|
||||||
`nack pli`, `ccm fir` and `goog-remb` RTCP feedback types, and act
|
`nack pli`, `ccm fir` and `goog-remb` RTCP feedback types, and act
|
||||||
accordingly.
|
accordingly.
|
||||||
|
|
||||||
The field `labels` is a dictionary that maps track mids to one of `audio`,
|
|
||||||
`video` or `screenshare`. If a track does not appear in the `labels`
|
|
||||||
dictionary, it should be ignored.
|
|
||||||
|
|
||||||
The receiver may either abort the stream immediately (see below), or send
|
The receiver may either abort the stream immediately (see below), or send
|
||||||
an answer.
|
an answer.
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ type Up interface {
|
||||||
AddLocal(Down) error
|
AddLocal(Down) error
|
||||||
DelLocal(Down) bool
|
DelLocal(Down) bool
|
||||||
Id() string
|
Id() string
|
||||||
|
Label() string
|
||||||
User() (string, string)
|
User() (string, string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +24,7 @@ type Up interface {
|
||||||
type UpTrack interface {
|
type UpTrack interface {
|
||||||
AddLocal(DownTrack) error
|
AddLocal(DownTrack) error
|
||||||
DelLocal(DownTrack) bool
|
DelLocal(DownTrack) bool
|
||||||
Label() string
|
Kind() webrtc.RTPCodecType
|
||||||
Codec() webrtc.RTPCodecCapability
|
Codec() webrtc.RTPCodecCapability
|
||||||
// get a recent packet. Returns 0 if the packet is not in cache.
|
// get a recent packet. Returns 0 if the packet is not in cache.
|
||||||
GetRTP(seqno uint16, result []byte) uint16
|
GetRTP(seqno uint16, result []byte) uint16
|
||||||
|
|
|
@ -313,6 +313,10 @@ func (up *rtpUpTrack) Label() string {
|
||||||
return up.label
|
return up.label
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (up *rtpUpTrack) Kind() webrtc.RTPCodecType {
|
||||||
|
return up.track.Kind()
|
||||||
|
}
|
||||||
|
|
||||||
func (up *rtpUpTrack) Codec() webrtc.RTPCodecCapability {
|
func (up *rtpUpTrack) Codec() webrtc.RTPCodecCapability {
|
||||||
return up.track.Codec().RTPCodecCapability
|
return up.track.Codec().RTPCodecCapability
|
||||||
}
|
}
|
||||||
|
@ -328,10 +332,10 @@ func (up *rtpUpTrack) hasRtcpFb(tpe, parameter string) bool {
|
||||||
|
|
||||||
type rtpUpConnection struct {
|
type rtpUpConnection struct {
|
||||||
id string
|
id string
|
||||||
|
label string
|
||||||
userId string
|
userId string
|
||||||
username string
|
username string
|
||||||
pc *webrtc.PeerConnection
|
pc *webrtc.PeerConnection
|
||||||
labels map[string]string
|
|
||||||
iceCandidates []*webrtc.ICECandidateInit
|
iceCandidates []*webrtc.ICECandidateInit
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
@ -363,6 +367,10 @@ func (up *rtpUpConnection) Id() string {
|
||||||
return up.id
|
return up.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (up *rtpUpConnection) Label() string {
|
||||||
|
return up.label
|
||||||
|
}
|
||||||
|
|
||||||
func (up *rtpUpConnection) User() (string, string) {
|
func (up *rtpUpConnection) User() (string, string) {
|
||||||
return up.userId, up.username
|
return up.userId, up.username
|
||||||
}
|
}
|
||||||
|
@ -413,33 +421,6 @@ func (up *rtpUpConnection) flushICECandidates() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTrackMid(pc *webrtc.PeerConnection, track *webrtc.TrackRemote) string {
|
|
||||||
for _, t := range pc.GetTransceivers() {
|
|
||||||
if t.Receiver() != nil && t.Receiver().Track() == track {
|
|
||||||
return t.Mid()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// called locked
|
|
||||||
func (up *rtpUpConnection) complete() bool {
|
|
||||||
for mid := range up.labels {
|
|
||||||
found := false
|
|
||||||
for _, t := range up.tracks {
|
|
||||||
m := getTrackMid(up.pc, t.track)
|
|
||||||
if m == mid {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// pushConnNow pushes a connection to all of the clients in a group
|
// pushConnNow pushes a connection to all of the clients in a group
|
||||||
func pushConnNow(up *rtpUpConnection, g *group.Group, cs []group.Client) {
|
func pushConnNow(up *rtpUpConnection, g *group.Group, cs []group.Client) {
|
||||||
up.mu.Lock()
|
up.mu.Lock()
|
||||||
|
@ -460,17 +441,11 @@ func pushConnNow(up *rtpUpConnection, g *group.Group, cs []group.Client) {
|
||||||
// pushConn schedules a call to pushConnNow
|
// pushConn schedules a call to pushConnNow
|
||||||
func pushConn(up *rtpUpConnection, g *group.Group, cs []group.Client) {
|
func pushConn(up *rtpUpConnection, g *group.Group, cs []group.Client) {
|
||||||
up.mu.Lock()
|
up.mu.Lock()
|
||||||
if up.complete() {
|
|
||||||
up.mu.Unlock()
|
|
||||||
pushConnNow(up, g, cs)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
up.pushed = false
|
up.pushed = false
|
||||||
up.mu.Unlock()
|
up.mu.Unlock()
|
||||||
|
|
||||||
go func(g *group.Group, cs []group.Client) {
|
go func(g *group.Group, cs []group.Client) {
|
||||||
time.Sleep(4 * time.Second)
|
time.Sleep(200 * time.Millisecond)
|
||||||
up.mu.Lock()
|
up.mu.Lock()
|
||||||
pushed := up.pushed
|
pushed := up.pushed
|
||||||
up.pushed = true
|
up.pushed = true
|
||||||
|
@ -481,7 +456,7 @@ func pushConn(up *rtpUpConnection, g *group.Group, cs []group.Client) {
|
||||||
}(g, cs)
|
}(g, cs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newUpConn(c group.Client, id string, labels map[string]string, offer string) (*rtpUpConnection, error) {
|
func newUpConn(c group.Client, id string, label string, offer string) (*rtpUpConnection, error) {
|
||||||
var o sdp.SessionDescription
|
var o sdp.SessionDescription
|
||||||
err := o.Unmarshal([]byte(offer))
|
err := o.Unmarshal([]byte(offer))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -506,31 +481,13 @@ func newUpConn(c group.Client, id string, labels map[string]string, offer string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
up := &rtpUpConnection{id: id, pc: pc, labels: labels}
|
up := &rtpUpConnection{id: id, label: label, pc: pc}
|
||||||
|
|
||||||
pc.OnTrack(func(remote *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
|
pc.OnTrack(func(remote *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
|
||||||
up.mu.Lock()
|
up.mu.Lock()
|
||||||
|
|
||||||
mid := getTrackMid(pc, remote)
|
|
||||||
if mid == "" {
|
|
||||||
log.Printf("Couldn't get track's mid")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
label, ok := up.labels[mid]
|
|
||||||
if !ok {
|
|
||||||
log.Printf("Couldn't get track's label")
|
|
||||||
isvideo := remote.Kind() == webrtc.RTPCodecTypeVideo
|
|
||||||
if isvideo {
|
|
||||||
label = "video"
|
|
||||||
} else {
|
|
||||||
label = "audio"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
track := &rtpUpTrack{
|
track := &rtpUpTrack{
|
||||||
track: remote,
|
track: remote,
|
||||||
label: label,
|
|
||||||
cache: packetcache.New(minPacketCache(remote)),
|
cache: packetcache.New(minPacketCache(remote)),
|
||||||
rate: estimator.New(time.Second),
|
rate: estimator.New(time.Second),
|
||||||
jitter: jitter.New(remote.Codec().ClockRate),
|
jitter: jitter.New(remote.Codec().ClockRate),
|
||||||
|
|
|
@ -59,7 +59,7 @@ type webClient struct {
|
||||||
password string
|
password string
|
||||||
permissions group.ClientPermissions
|
permissions group.ClientPermissions
|
||||||
status map[string]interface{}
|
status map[string]interface{}
|
||||||
requested map[string]uint32
|
requested map[string][]string
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
writeCh chan interface{}
|
writeCh chan interface{}
|
||||||
writerDone chan struct{}
|
writerDone chan struct{}
|
||||||
|
@ -122,53 +122,6 @@ func (c *webClient) PushClient(id, username string, permissions group.ClientPerm
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type rateMap map[string]uint32
|
|
||||||
|
|
||||||
func (v *rateMap) UnmarshalJSON(b []byte) error {
|
|
||||||
var m map[string]interface{}
|
|
||||||
|
|
||||||
err := json.Unmarshal(b, &m)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
n := make(map[string]uint32, len(m))
|
|
||||||
for k, w := range m {
|
|
||||||
switch w := w.(type) {
|
|
||||||
case bool:
|
|
||||||
if w {
|
|
||||||
n[k] = ^uint32(0)
|
|
||||||
} else {
|
|
||||||
n[k] = 0
|
|
||||||
}
|
|
||||||
case float64:
|
|
||||||
if w < 0 || w >= float64(^uint32(0)) {
|
|
||||||
return errors.New("overflow")
|
|
||||||
}
|
|
||||||
n[k] = uint32(w)
|
|
||||||
default:
|
|
||||||
return errors.New("unexpected type in JSON map")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*v = n
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v rateMap) MarshalJSON() ([]byte, error) {
|
|
||||||
m := make(map[string]interface{}, len(v))
|
|
||||||
for k, w := range v {
|
|
||||||
switch w {
|
|
||||||
case 0:
|
|
||||||
m[k] = false
|
|
||||||
case ^uint32(0):
|
|
||||||
m[k] = true
|
|
||||||
default:
|
|
||||||
m[k] = w
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return json.Marshal(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
type clientMessage struct {
|
type clientMessage struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Kind string `json:"kind,omitempty"`
|
Kind string `json:"kind,omitempty"`
|
||||||
|
@ -187,8 +140,8 @@ type clientMessage struct {
|
||||||
Time int64 `json:"time,omitempty"`
|
Time int64 `json:"time,omitempty"`
|
||||||
SDP string `json:"sdp,omitempty"`
|
SDP string `json:"sdp,omitempty"`
|
||||||
Candidate *webrtc.ICECandidateInit `json:"candidate,omitempty"`
|
Candidate *webrtc.ICECandidateInit `json:"candidate,omitempty"`
|
||||||
Labels map[string]string `json:"labels,omitempty"`
|
Label string `json:"label,omitempty"`
|
||||||
Request rateMap `json:"request,omitempty"`
|
Request map[string][]string `json:"request,omitempty"`
|
||||||
RTCConfiguration *webrtc.Configuration `json:"rtcConfiguration,omitempty"`
|
RTCConfiguration *webrtc.Configuration `json:"rtcConfiguration,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,7 +169,7 @@ func getUpConns(c *webClient) []*rtpUpConnection {
|
||||||
return up
|
return up
|
||||||
}
|
}
|
||||||
|
|
||||||
func addUpConn(c *webClient, id string, labels map[string]string, offer string) (*rtpUpConnection, bool, error) {
|
func addUpConn(c *webClient, id, label string, offer string) (*rtpUpConnection, bool, error) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
@ -232,7 +185,7 @@ func addUpConn(c *webClient, id string, labels map[string]string, offer string)
|
||||||
return old, false, nil
|
return old, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := newUpConn(c, id, labels, offer)
|
conn, err := newUpConn(c, id, label, offer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
@ -540,33 +493,16 @@ func negotiate(c *webClient, down *rtpDownConnection, restartIce bool, replace s
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
labels := make(map[string]string)
|
|
||||||
for _, t := range down.pc.GetTransceivers() {
|
|
||||||
var track webrtc.TrackLocal
|
|
||||||
if t.Sender() != nil {
|
|
||||||
track = t.Sender().Track()
|
|
||||||
}
|
|
||||||
if track == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tr := range down.tracks {
|
|
||||||
if tr.track == track {
|
|
||||||
labels[t.Mid()] = tr.remote.Label()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
source, username := down.remote.User()
|
source, username := down.remote.User()
|
||||||
|
|
||||||
return c.write(clientMessage{
|
return c.write(clientMessage{
|
||||||
Type: "offer",
|
Type: "offer",
|
||||||
Id: down.id,
|
Id: down.id,
|
||||||
|
Label: down.remote.Label(),
|
||||||
Replace: replace,
|
Replace: replace,
|
||||||
Source: source,
|
Source: source,
|
||||||
Username: username,
|
Username: username,
|
||||||
SDP: down.pc.LocalDescription().SDP,
|
SDP: down.pc.LocalDescription().SDP,
|
||||||
Labels: labels,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -582,8 +518,8 @@ func sendICE(c *webClient, id string, candidate *webrtc.ICECandidate) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func gotOffer(c *webClient, id string, sdp string, labels map[string]string, replace string) error {
|
func gotOffer(c *webClient, id, label string, sdp string, replace string) error {
|
||||||
up, _, err := addUpConn(c, id, labels, sdp)
|
up, _, err := addUpConn(c, id, label, sdp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -681,7 +617,7 @@ func gotICE(c *webClient, candidate *webrtc.ICECandidateInit, id string) error {
|
||||||
return conn.addICECandidate(candidate)
|
return conn.addICECandidate(candidate)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *webClient) setRequested(requested map[string]uint32) error {
|
func (c *webClient) setRequested(requested map[string][]string) error {
|
||||||
if c.group == nil {
|
if c.group == nil {
|
||||||
return errors.New("attempted to request with no group joined")
|
return errors.New("attempted to request with no group joined")
|
||||||
}
|
}
|
||||||
|
@ -701,8 +637,46 @@ func pushConns(c group.Client, g *group.Group) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *webClient) isRequested(label string) bool {
|
func requestedTracks(c *webClient, up conn.Up, tracks []conn.UpTrack) []conn.UpTrack {
|
||||||
return c.requested[label] != 0
|
r, ok := c.requested[up.Label()]
|
||||||
|
if !ok {
|
||||||
|
r, ok = c.requested[""]
|
||||||
|
}
|
||||||
|
if !ok || len(r) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var audio, video bool
|
||||||
|
for _, s := range r {
|
||||||
|
switch s {
|
||||||
|
case "audio":
|
||||||
|
audio = true
|
||||||
|
case "video":
|
||||||
|
video = true
|
||||||
|
default:
|
||||||
|
log.Printf("client requested unknown value %v", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ts []conn.UpTrack
|
||||||
|
if audio {
|
||||||
|
for _, t := range tracks {
|
||||||
|
if t.Kind() == webrtc.RTPCodecTypeAudio {
|
||||||
|
ts = append(ts, t)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if video {
|
||||||
|
for _, t := range tracks {
|
||||||
|
if t.Kind() == webrtc.RTPCodecTypeVideo {
|
||||||
|
ts = append(ts, t)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ts
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *webClient) PushConn(g *group.Group, id string, up conn.Up, tracks []conn.UpTrack, replace string) error {
|
func (c *webClient) PushConn(g *group.Group, id string, up conn.Up, tracks []conn.UpTrack, replace string) error {
|
||||||
|
@ -871,16 +845,7 @@ func handleAction(c *webClient, a interface{}) error {
|
||||||
}
|
}
|
||||||
var tracks []conn.UpTrack
|
var tracks []conn.UpTrack
|
||||||
if a.conn != nil {
|
if a.conn != nil {
|
||||||
tracks = make([]conn.UpTrack,
|
tracks = requestedTracks(c, a.conn, a.tracks)
|
||||||
0, len(a.tracks),
|
|
||||||
)
|
|
||||||
for _, t := range a.tracks {
|
|
||||||
if c.isRequested(t.Label()) {
|
|
||||||
tracks = append(
|
|
||||||
tracks, t,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(tracks) == 0 {
|
if len(tracks) == 0 {
|
||||||
|
@ -923,9 +888,6 @@ func handleAction(c *webClient, a interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, u := range c.up {
|
for _, u := range c.up {
|
||||||
if !u.complete() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
tracks := u.getTracks()
|
tracks := u.getTracks()
|
||||||
replace := u.getReplace(false)
|
replace := u.getReplace(false)
|
||||||
|
|
||||||
|
@ -1051,7 +1013,7 @@ func leaveGroup(c *webClient) {
|
||||||
group.DelClient(c)
|
group.DelClient(c)
|
||||||
c.permissions = group.ClientPermissions{}
|
c.permissions = group.ClientPermissions{}
|
||||||
c.status = nil
|
c.status = nil
|
||||||
c.requested = map[string]uint32{}
|
c.requested = make(map[string][]string)
|
||||||
c.group = nil
|
c.group = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1238,7 +1200,7 @@ func handleClientMessage(c *webClient, m clientMessage) error {
|
||||||
})
|
})
|
||||||
return c.error(group.UserError("not authorised"))
|
return c.error(group.UserError("not authorised"))
|
||||||
}
|
}
|
||||||
err := gotOffer(c, m.Id, m.SDP, m.Labels, m.Replace)
|
err := gotOffer(c, m.Id, m.Label, m.SDP, m.Replace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("gotOffer: %v", err)
|
log.Printf("gotOffer: %v", err)
|
||||||
return failUpConnection(c, m.Id, "negotiation failed")
|
return failUpConnection(c, m.Id, "negotiation failed")
|
||||||
|
|
|
@ -342,13 +342,7 @@ function gotDownStream(c) {
|
||||||
setMedia(c, false);
|
setMedia(c, false);
|
||||||
};
|
};
|
||||||
c.onnegotiationcompleted = function() {
|
c.onnegotiationcompleted = function() {
|
||||||
let found = false;
|
resetMedia(c);
|
||||||
for(let key in c.labels) {
|
|
||||||
if(c.labels[key] === 'video')
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
if(!found)
|
|
||||||
resetMedia(c);
|
|
||||||
}
|
}
|
||||||
c.onstatus = function(status) {
|
c.onstatus = function(status) {
|
||||||
setMediaStatus(c);
|
setMediaStatus(c);
|
||||||
|
@ -384,7 +378,7 @@ getButtonElement('presentbutton').onclick = async function(e) {
|
||||||
// button a second time before the stream is set up and the button hidden.
|
// button a second time before the stream is set up and the button hidden.
|
||||||
button.disabled = true;
|
button.disabled = true;
|
||||||
try {
|
try {
|
||||||
let id = findUpMedia('local');
|
let id = findUpMedia('camera');
|
||||||
if(!id)
|
if(!id)
|
||||||
await addLocalMedia();
|
await addLocalMedia();
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -394,12 +388,12 @@ getButtonElement('presentbutton').onclick = async function(e) {
|
||||||
|
|
||||||
getButtonElement('unpresentbutton').onclick = function(e) {
|
getButtonElement('unpresentbutton').onclick = function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
closeUpMediaKind('local');
|
closeUpMedia('camera');
|
||||||
resizePeers();
|
resizePeers();
|
||||||
};
|
};
|
||||||
|
|
||||||
function changePresentation() {
|
function changePresentation() {
|
||||||
let c = findUpMedia('local');
|
let c = findUpMedia('camera');
|
||||||
if(c)
|
if(c)
|
||||||
addLocalMedia(c.localId);
|
addLocalMedia(c.localId);
|
||||||
}
|
}
|
||||||
|
@ -419,7 +413,7 @@ function setVisibility(id, visible) {
|
||||||
function setButtonsVisibility() {
|
function setButtonsVisibility() {
|
||||||
let connected = serverConnection && serverConnection.socket;
|
let connected = serverConnection && serverConnection.socket;
|
||||||
let permissions = serverConnection.permissions;
|
let permissions = serverConnection.permissions;
|
||||||
let local = !!findUpMedia('local');
|
let local = !!findUpMedia('camera');
|
||||||
let video = !!findUpMedia('video');
|
let video = !!findUpMedia('video');
|
||||||
let canWebrtc = !(typeof RTCPeerConnection === 'undefined');
|
let canWebrtc = !(typeof RTCPeerConnection === 'undefined');
|
||||||
let canFile =
|
let canFile =
|
||||||
|
@ -516,7 +510,7 @@ document.getElementById('sharebutton').onclick = function(e) {
|
||||||
|
|
||||||
document.getElementById('stopvideobutton').onclick = function(e) {
|
document.getElementById('stopvideobutton').onclick = function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
closeUpMediaKind('video');
|
closeUpMedia('video');
|
||||||
resizePeers();
|
resizePeers();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -556,12 +550,37 @@ getSelectElement('sendselect').onchange = async function(e) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} what
|
||||||
|
* @returns {Object<string,Array<string>>}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function mapRequest(what) {
|
||||||
|
switch(what) {
|
||||||
|
case '':
|
||||||
|
return {};
|
||||||
|
break;
|
||||||
|
case 'audio':
|
||||||
|
return {'': ['audio']};
|
||||||
|
break;
|
||||||
|
case 'screenshare':
|
||||||
|
return {screenshare: ['audio','video'], '': ['audio']};
|
||||||
|
break;
|
||||||
|
case 'everything':
|
||||||
|
return {'': ['audio','video']}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
displayError(`Unknown value ${what} in request`);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getSelectElement('requestselect').onchange = function(e) {
|
getSelectElement('requestselect').onchange = function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if(!(this instanceof HTMLSelectElement))
|
if(!(this instanceof HTMLSelectElement))
|
||||||
throw new Error('Unexpected type for this');
|
throw new Error('Unexpected type for this');
|
||||||
updateSettings({request: this.value});
|
updateSettings({request: this.value});
|
||||||
serverConnection.request(this.value);
|
serverConnection.request(mapRequest(this.value));
|
||||||
};
|
};
|
||||||
|
|
||||||
const activityDetectionInterval = 200;
|
const activityDetectionInterval = 200;
|
||||||
|
@ -1069,8 +1088,8 @@ async function addLocalMedia(localId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
c.kind = 'local';
|
|
||||||
c.stream = stream;
|
c.stream = stream;
|
||||||
|
c.label = 'camera';
|
||||||
|
|
||||||
if(filter) {
|
if(filter) {
|
||||||
try {
|
try {
|
||||||
|
@ -1100,7 +1119,6 @@ async function addLocalMedia(localId) {
|
||||||
|
|
||||||
let mute = getSettings().localMute;
|
let mute = getSettings().localMute;
|
||||||
c.stream.getTracks().forEach(t => {
|
c.stream.getTracks().forEach(t => {
|
||||||
c.labels[t.id] = t.kind;
|
|
||||||
if(t.kind == 'audio') {
|
if(t.kind == 'audio') {
|
||||||
if(mute)
|
if(mute)
|
||||||
t.enabled = false;
|
t.enabled = false;
|
||||||
|
@ -1143,8 +1161,8 @@ async function addShareMedia() {
|
||||||
}
|
}
|
||||||
|
|
||||||
let c = newUpStream();
|
let c = newUpStream();
|
||||||
c.kind = 'screenshare';
|
|
||||||
c.stream = stream;
|
c.stream = stream;
|
||||||
|
c.label = 'screenshare';
|
||||||
c.onclose = replace => {
|
c.onclose = replace => {
|
||||||
stopStream(stream);
|
stopStream(stream);
|
||||||
if(!replace)
|
if(!replace)
|
||||||
|
@ -1153,7 +1171,6 @@ async function addShareMedia() {
|
||||||
stream.getTracks().forEach(t => {
|
stream.getTracks().forEach(t => {
|
||||||
c.pc.addTrack(t, stream);
|
c.pc.addTrack(t, stream);
|
||||||
t.onended = e => c.close();
|
t.onended = e => c.close();
|
||||||
c.labels[t.id] = 'screenshare';
|
|
||||||
});
|
});
|
||||||
c.onstats = gotUpStats;
|
c.onstats = gotUpStats;
|
||||||
c.setStatsInterval(2000);
|
c.setStatsInterval(2000);
|
||||||
|
@ -1184,8 +1201,8 @@ async function addFileMedia(file) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let c = newUpStream();
|
let c = newUpStream();
|
||||||
c.kind = 'video';
|
|
||||||
c.stream = stream;
|
c.stream = stream;
|
||||||
|
c.label = 'video';
|
||||||
c.onclose = function(replace) {
|
c.onclose = function(replace) {
|
||||||
stopStream(c.stream);
|
stopStream(c.stream);
|
||||||
let media = /** @type{HTMLVideoElement} */
|
let media = /** @type{HTMLVideoElement} */
|
||||||
|
@ -1201,7 +1218,7 @@ async function addFileMedia(file) {
|
||||||
stream.onaddtrack = function(e) {
|
stream.onaddtrack = function(e) {
|
||||||
let t = e.track;
|
let t = e.track;
|
||||||
if(t.kind === 'audio') {
|
if(t.kind === 'audio') {
|
||||||
let presenting = !!findUpMedia('local');
|
let presenting = !!findUpMedia('camera');
|
||||||
let muted = getSettings().localMute;
|
let muted = getSettings().localMute;
|
||||||
if(presenting && !muted) {
|
if(presenting && !muted) {
|
||||||
setLocalMute(true, true);
|
setLocalMute(true, true);
|
||||||
|
@ -1209,13 +1226,11 @@ async function addFileMedia(file) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.pc.addTrack(t, stream);
|
c.pc.addTrack(t, stream);
|
||||||
c.labels[t.id] = t.kind;
|
|
||||||
c.onstats = gotUpStats;
|
c.onstats = gotUpStats;
|
||||||
c.setStatsInterval(2000);
|
c.setStatsInterval(2000);
|
||||||
};
|
};
|
||||||
stream.onremovetrack = function(e) {
|
stream.onremovetrack = function(e) {
|
||||||
let t = e.track;
|
let t = e.track;
|
||||||
delete(c.labels[t.id]);
|
|
||||||
|
|
||||||
/** @type {RTCRtpSender} */
|
/** @type {RTCRtpSender} */
|
||||||
let sender;
|
let sender;
|
||||||
|
@ -1229,7 +1244,12 @@ async function addFileMedia(file) {
|
||||||
console.warn('Removing unknown track');
|
console.warn('Removing unknown track');
|
||||||
}
|
}
|
||||||
|
|
||||||
if(Object.keys(c.labels).length === 0) {
|
let found = false;
|
||||||
|
c.pc.getSenders().forEach(s => {
|
||||||
|
if(s.track)
|
||||||
|
found = true;
|
||||||
|
});
|
||||||
|
if(!found) {
|
||||||
stream.onaddtrack = null;
|
stream.onaddtrack = null;
|
||||||
stream.onremovetrack = null;
|
stream.onremovetrack = null;
|
||||||
c.close();
|
c.close();
|
||||||
|
@ -1254,28 +1274,28 @@ function stopStream(s) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* closeUpMediaKind closes all up connections that correspond to a given
|
* closeUpMedia closes all up connections with the given label. If label
|
||||||
* kind of media. If kind is null, it closes all up connections.
|
* is null, it closes all up connections.
|
||||||
*
|
*
|
||||||
* @param {string} kind
|
* @param {string} label
|
||||||
*/
|
*/
|
||||||
function closeUpMediaKind(kind) {
|
function closeUpMedia(label) {
|
||||||
for(let id in serverConnection.up) {
|
for(let id in serverConnection.up) {
|
||||||
let c = serverConnection.up[id];
|
let c = serverConnection.up[id];
|
||||||
if(kind && c.kind != kind)
|
if(label && c.label !== label)
|
||||||
continue
|
continue
|
||||||
c.close();
|
c.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} kind
|
* @param {string} label
|
||||||
* @returns {Stream}
|
* @returns {Stream}
|
||||||
*/
|
*/
|
||||||
function findUpMedia(kind) {
|
function findUpMedia(label) {
|
||||||
for(let id in serverConnection.up) {
|
for(let id in serverConnection.up) {
|
||||||
let c = serverConnection.up[id]
|
let c = serverConnection.up[id]
|
||||||
if(c.kind === kind)
|
if(c.label === label)
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -1289,7 +1309,7 @@ function muteLocalTracks(mute) {
|
||||||
return;
|
return;
|
||||||
for(let id in serverConnection.up) {
|
for(let id in serverConnection.up) {
|
||||||
let c = serverConnection.up[id];
|
let c = serverConnection.up[id];
|
||||||
if(c.kind === 'local') {
|
if(c.label === 'camera') {
|
||||||
let stream = c.stream;
|
let stream = c.stream;
|
||||||
stream.getTracks().forEach(t => {
|
stream.getTracks().forEach(t => {
|
||||||
if(t.kind === 'audio') {
|
if(t.kind === 'audio') {
|
||||||
|
@ -1370,7 +1390,7 @@ async function setMedia(c, isUp, mirror, video) {
|
||||||
showVideo();
|
showVideo();
|
||||||
resizePeers();
|
resizePeers();
|
||||||
|
|
||||||
if(!isUp && isSafari() && !findUpMedia('local')) {
|
if(!isUp && isSafari() && !findUpMedia('camera')) {
|
||||||
// Safari doesn't allow autoplay unless the user has granted media access
|
// Safari doesn't allow autoplay unless the user has granted media access
|
||||||
try {
|
try {
|
||||||
let stream = await navigator.mediaDevices.getUserMedia({audio: true});
|
let stream = await navigator.mediaDevices.getUserMedia({audio: true});
|
||||||
|
@ -1429,10 +1449,10 @@ function addCustomControls(media, container, c) {
|
||||||
|
|
||||||
let volume = getVideoButton(controls, 'volume');
|
let volume = getVideoButton(controls, 'volume');
|
||||||
let stopsharing = getVideoButton(topcontrols, 'video-stop');
|
let stopsharing = getVideoButton(topcontrols, 'video-stop');
|
||||||
if (c.kind !== "screenshare") {
|
if (c.label !== "screenshare") {
|
||||||
stopsharing.remove();
|
stopsharing.remove();
|
||||||
}
|
}
|
||||||
if(c.kind === 'local') {
|
if(c.label === 'camera') {
|
||||||
volume.remove();
|
volume.remove();
|
||||||
} else {
|
} else {
|
||||||
setVolumeButton(media.muted,
|
setVolumeButton(media.muted,
|
||||||
|
@ -1820,9 +1840,9 @@ async function gotJoined(kind, group, perms, message) {
|
||||||
if(typeof RTCPeerConnection === 'undefined')
|
if(typeof RTCPeerConnection === 'undefined')
|
||||||
displayWarning("This browser doesn't support WebRTC");
|
displayWarning("This browser doesn't support WebRTC");
|
||||||
else
|
else
|
||||||
this.request(getSettings().request);
|
this.request(mapRequest(getSettings().request));
|
||||||
|
|
||||||
if(serverConnection.permissions.present && !findUpMedia('local')) {
|
if(serverConnection.permissions.present && !findUpMedia('camera')) {
|
||||||
if(present) {
|
if(present) {
|
||||||
if(present === 'mike')
|
if(present === 'mike')
|
||||||
updateSettings({video: ''});
|
updateSettings({video: ''});
|
||||||
|
|
|
@ -212,8 +212,8 @@ function ServerConnection() {
|
||||||
* @property {boolean} [noecho]
|
* @property {boolean} [noecho]
|
||||||
* @property {string} [sdp]
|
* @property {string} [sdp]
|
||||||
* @property {RTCIceCandidate} [candidate]
|
* @property {RTCIceCandidate} [candidate]
|
||||||
* @property {Object<string,string>} [labels]
|
* @property {string} [label]
|
||||||
* @property {Object<string,(boolean|number)>} [request]
|
* @property {Object<string,Array<string>>} [request]
|
||||||
* @property {Object<string,any>} [rtcConfiguration]
|
* @property {Object<string,any>} [rtcConfiguration]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -296,7 +296,7 @@ ServerConnection.prototype.connect = async function(url) {
|
||||||
case 'handshake':
|
case 'handshake':
|
||||||
break;
|
break;
|
||||||
case 'offer':
|
case 'offer':
|
||||||
sc.gotOffer(m.id, m.labels, m.source, m.username,
|
sc.gotOffer(m.id, m.label, m.source, m.username,
|
||||||
m.sdp, m.replace);
|
m.sdp, m.replace);
|
||||||
break;
|
break;
|
||||||
case 'answer':
|
case 'answer':
|
||||||
|
@ -439,32 +439,14 @@ ServerConnection.prototype.leave = function(group) {
|
||||||
/**
|
/**
|
||||||
* request sets the list of requested media types.
|
* request sets the list of requested media types.
|
||||||
*
|
*
|
||||||
* @param {string} what - One of '', 'audio', 'screenshare' or 'everything'.
|
* @param {Object<string,Array<string>>} what
|
||||||
|
* - A dictionary that maps labels to a sequence of 'audio' and 'video'.
|
||||||
|
* An entry with an empty label '' provides the default.
|
||||||
*/
|
*/
|
||||||
ServerConnection.prototype.request = function(what) {
|
ServerConnection.prototype.request = function(what) {
|
||||||
/** @type {Object<string,boolean>} */
|
|
||||||
let request = {};
|
|
||||||
switch(what) {
|
|
||||||
case '':
|
|
||||||
request = {};
|
|
||||||
break;
|
|
||||||
case 'audio':
|
|
||||||
request = {audio: true};
|
|
||||||
break;
|
|
||||||
case 'screenshare':
|
|
||||||
request = {audio: true, screenshare: true};
|
|
||||||
break;
|
|
||||||
case 'everything':
|
|
||||||
request = {audio: true, screenshare: true, video: true};
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.error(`Unknown value ${what} in request`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.send({
|
this.send({
|
||||||
type: 'request',
|
type: 'request',
|
||||||
request: request,
|
request: what,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -622,14 +604,14 @@ ServerConnection.prototype.groupAction = function(kind, message) {
|
||||||
* Called when we receive an offer from the server. Don't call this.
|
* Called when we receive an offer from the server. Don't call this.
|
||||||
*
|
*
|
||||||
* @param {string} id
|
* @param {string} id
|
||||||
* @param {Object<string, string>} labels
|
* @param {string} label
|
||||||
* @param {string} source
|
* @param {string} source
|
||||||
* @param {string} username
|
* @param {string} username
|
||||||
* @param {string} sdp
|
* @param {string} sdp
|
||||||
* @param {string} replace
|
* @param {string} replace
|
||||||
* @function
|
* @function
|
||||||
*/
|
*/
|
||||||
ServerConnection.prototype.gotOffer = async function(id, labels, source, username, sdp, replace) {
|
ServerConnection.prototype.gotOffer = async function(id, label, source, username, sdp, replace) {
|
||||||
let sc = this;
|
let sc = this;
|
||||||
|
|
||||||
if(sc.up[id]) {
|
if(sc.up[id]) {
|
||||||
|
@ -669,6 +651,7 @@ ServerConnection.prototype.gotOffer = async function(id, labels, source, usernam
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
c = new Stream(this, id, oldLocalId || newLocalId(), pc, false);
|
c = new Stream(this, id, oldLocalId || newLocalId(), pc, false);
|
||||||
|
c.label = label;
|
||||||
sc.down[id] = c;
|
sc.down[id] = c;
|
||||||
|
|
||||||
c.pc.onicecandidate = function(e) {
|
c.pc.onicecandidate = function(e) {
|
||||||
|
@ -689,32 +672,15 @@ ServerConnection.prototype.gotOffer = async function(id, labels, source, usernam
|
||||||
};
|
};
|
||||||
|
|
||||||
c.pc.ontrack = function(e) {
|
c.pc.ontrack = function(e) {
|
||||||
let label = e.transceiver && c.labelsByMid[e.transceiver.mid];
|
|
||||||
if(label) {
|
|
||||||
c.labels[e.track.id] = label;
|
|
||||||
} else {
|
|
||||||
console.warn("Couldn't find label for track");
|
|
||||||
}
|
|
||||||
c.stream = e.streams[0];
|
c.stream = e.streams[0];
|
||||||
if(c.ondowntrack) {
|
if(c.ondowntrack) {
|
||||||
c.ondowntrack.call(
|
c.ondowntrack.call(
|
||||||
c, e.track, e.transceiver, label, e.streams[0],
|
c, e.track, e.transceiver, e.streams[0],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
c.labelsByMid = labels;
|
|
||||||
c.labels = {};
|
|
||||||
c.pc.getTransceivers().forEach(transceiver => {
|
|
||||||
let label = c.labelsByMid[transceiver.mid];
|
|
||||||
let track = transceiver.receiver && transceiver.receiver.track;
|
|
||||||
if(label && track) {
|
|
||||||
c.labels[track.id] = label;
|
|
||||||
} else if(!track) {
|
|
||||||
console.warn("Couldn't find track for label");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
c.source = source;
|
c.source = source;
|
||||||
c.username = username;
|
c.username = username;
|
||||||
|
|
||||||
|
@ -883,12 +849,6 @@ function Stream(sc, id, localId, pc, up) {
|
||||||
* @const
|
* @const
|
||||||
*/
|
*/
|
||||||
this.up = up;
|
this.up = up;
|
||||||
/**
|
|
||||||
* For up streams, one of "local" or "screenshare".
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
this.kind = null;
|
|
||||||
/**
|
/**
|
||||||
* For down streams, the id of the client that created the stream.
|
* For down streams, the id of the client that created the stream.
|
||||||
*
|
*
|
||||||
|
@ -916,17 +876,11 @@ function Stream(sc, id, localId, pc, up) {
|
||||||
*/
|
*/
|
||||||
this.stream = null;
|
this.stream = null;
|
||||||
/**
|
/**
|
||||||
* Track labels, indexed by track id.
|
* The label assigned by the originator to this stream.
|
||||||
*
|
*
|
||||||
* @type {Object<string,string>}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
this.labels = {};
|
this.label = null;
|
||||||
/**
|
|
||||||
* Track labels, indexed by mid.
|
|
||||||
*
|
|
||||||
* @type {Object<string,string>}
|
|
||||||
*/
|
|
||||||
this.labelsByMid = {};
|
|
||||||
/**
|
/**
|
||||||
* The id of the stream that we are currently replacing.
|
* The id of the stream that we are currently replacing.
|
||||||
*
|
*
|
||||||
|
@ -1004,7 +958,7 @@ function Stream(sc, id, localId, pc, up) {
|
||||||
* If the stream parameter differs from its previous value, then it
|
* If the stream parameter differs from its previous value, then it
|
||||||
* indicates that the old stream has been discarded.
|
* indicates that the old stream has been discarded.
|
||||||
*
|
*
|
||||||
* @type{(this: Stream, track: MediaStreamTrack, transceiver: RTCRtpTransceiver, label: string, stream: MediaStream) => void}
|
* @type{(this: Stream, track: MediaStreamTrack, transceiver: RTCRtpTransceiver, stream: MediaStream) => void}
|
||||||
*/
|
*/
|
||||||
this.ondowntrack = null;
|
this.ondowntrack = null;
|
||||||
/**
|
/**
|
||||||
|
@ -1164,17 +1118,6 @@ Stream.prototype.negotiate = async function (restartIce) {
|
||||||
throw(new Error("Didn't create offer"));
|
throw(new Error("Didn't create offer"));
|
||||||
await c.pc.setLocalDescription(offer);
|
await c.pc.setLocalDescription(offer);
|
||||||
|
|
||||||
// mids are not known until this point
|
|
||||||
c.pc.getTransceivers().forEach(t => {
|
|
||||||
if(t.sender && t.sender.track) {
|
|
||||||
let label = c.labels[t.sender.track.id];
|
|
||||||
if(label)
|
|
||||||
c.labelsByMid[t.mid] = label;
|
|
||||||
else
|
|
||||||
console.warn("Couldn't find label for track");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
c.sc.send({
|
c.sc.send({
|
||||||
type: 'offer',
|
type: 'offer',
|
||||||
source: c.sc.id,
|
source: c.sc.id,
|
||||||
|
@ -1182,7 +1125,7 @@ Stream.prototype.negotiate = async function (restartIce) {
|
||||||
kind: this.localDescriptionSent ? 'renegotiate' : '',
|
kind: this.localDescriptionSent ? 'renegotiate' : '',
|
||||||
id: c.id,
|
id: c.id,
|
||||||
replace: this.replace,
|
replace: this.replace,
|
||||||
labels: c.labelsByMid,
|
label: c.label,
|
||||||
sdp: c.pc.localDescription.sdp,
|
sdp: c.pc.localDescription.sdp,
|
||||||
});
|
});
|
||||||
this.localDescriptionSent = true;
|
this.localDescriptionSent = true;
|
||||||
|
|
Loading…
Reference in a new issue