mirror of
https://github.com/pion/webrtc.git
synced 2025-10-05 23:26:58 +08:00
DataChannels: OpenChannel & OnOpen
- OpenChannel messages are now sent after SCTP is Established. - The OnOpen handler tells the application when a channel has opened. Relates to #159
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
@@ -34,22 +35,25 @@ func main() {
|
|||||||
// Set the handler for ICE connection state
|
// Set the handler for ICE connection state
|
||||||
// This will notify you when the peer has connected/disconnected
|
// This will notify you when the peer has connected/disconnected
|
||||||
peerConnection.OnICEConnectionStateChange = func(connectionState ice.ConnectionState) {
|
peerConnection.OnICEConnectionStateChange = func(connectionState ice.ConnectionState) {
|
||||||
fmt.Printf("Connection State has changed %s \n", connectionState.String())
|
fmt.Printf("ICE Connection State has changed: %s\n", connectionState.String())
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: find the correct place for this
|
dataChannel.Lock()
|
||||||
if connectionState == ice.ConnectionStateConnected {
|
|
||||||
time.AfterFunc(3*time.Second, func() {
|
// Register channel opening handling
|
||||||
fmt.Println("sending openchannel")
|
dataChannel.OnOpen = func() {
|
||||||
err := dataChannel.SendOpenChannelMessage()
|
fmt.Printf("Data channel '%s'-'%d' open. Random messages will now be sent to any connected DataChannels every 5 seconds\n", dataChannel.Label, dataChannel.ID)
|
||||||
if err != nil {
|
for {
|
||||||
fmt.Println("faild to send openchannel", err)
|
time.Sleep(5 * time.Second)
|
||||||
}
|
message := randSeq(15)
|
||||||
})
|
fmt.Printf("Sending %s \n", message)
|
||||||
|
|
||||||
|
err := dataChannel.Send(datachannel.PayloadString{Data: []byte(message)})
|
||||||
|
check(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the Onmessage to handle incoming messages
|
// Register the Onmessage to handle incoming messages
|
||||||
dataChannel.Lock()
|
|
||||||
dataChannel.Onmessage = func(payload datachannel.Payload) {
|
dataChannel.Onmessage = func(payload datachannel.Payload) {
|
||||||
switch p := payload.(type) {
|
switch p := payload.(type) {
|
||||||
case *datachannel.PayloadString:
|
case *datachannel.PayloadString:
|
||||||
@@ -60,6 +64,7 @@ func main() {
|
|||||||
fmt.Printf("Message '%s' from DataChannel '%s' no payload \n", p.PayloadType().String(), dataChannel.Label)
|
fmt.Printf("Message '%s' from DataChannel '%s' no payload \n", p.PayloadType().String(), dataChannel.Label)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dataChannel.Unlock()
|
dataChannel.Unlock()
|
||||||
|
|
||||||
// Create an offer to send to the browser
|
// Create an offer to send to the browser
|
||||||
@@ -69,7 +74,7 @@ func main() {
|
|||||||
// Output the offer in base64 so we can paste it in browser
|
// Output the offer in base64 so we can paste it in browser
|
||||||
fmt.Println(base64.StdEncoding.EncodeToString([]byte(offer.Sdp)))
|
fmt.Println(base64.StdEncoding.EncodeToString([]byte(offer.Sdp)))
|
||||||
|
|
||||||
// Wait for the offer to be pasted
|
// Wait for the answer to be pasted
|
||||||
sd := mustReadStdin()
|
sd := mustReadStdin()
|
||||||
|
|
||||||
// Set the remote SessionDescription
|
// Set the remote SessionDescription
|
||||||
@@ -82,16 +87,8 @@ func main() {
|
|||||||
err = peerConnection.SetRemoteDescription(answer)
|
err = peerConnection.SetRemoteDescription(answer)
|
||||||
check(err)
|
check(err)
|
||||||
|
|
||||||
// Send messages every 5 seconds
|
// Block forever
|
||||||
fmt.Println("Random messages will now be sent to any connected DataChannels every 5 seconds")
|
select {}
|
||||||
for {
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
message := randSeq(15)
|
|
||||||
fmt.Printf("Sending %s \n", message)
|
|
||||||
|
|
||||||
err := dataChannel.Send(datachannel.PayloadString{Data: []byte(message)})
|
|
||||||
check(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// randSeq is used to generate a random message
|
// randSeq is used to generate a random message
|
||||||
@@ -109,12 +106,15 @@ func randSeq(n int) string {
|
|||||||
func mustReadStdin() string {
|
func mustReadStdin() string {
|
||||||
reader := bufio.NewReader(os.Stdin)
|
reader := bufio.NewReader(os.Stdin)
|
||||||
rawSd, err := reader.ReadString('\n')
|
rawSd, err := reader.ReadString('\n')
|
||||||
check(err)
|
if err != io.EOF {
|
||||||
|
check(err)
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
sd, err := base64.StdEncoding.DecodeString(rawSd)
|
|
||||||
|
|
||||||
|
sd, err := base64.StdEncoding.DecodeString(rawSd)
|
||||||
check(err)
|
check(err)
|
||||||
|
|
||||||
return string(sd)
|
return string(sd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -7,7 +7,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pions/webrtc"
|
"github.com/pions/webrtc"
|
||||||
@@ -15,61 +14,52 @@ import (
|
|||||||
"github.com/pions/webrtc/pkg/ice"
|
"github.com/pions/webrtc/pkg/ice"
|
||||||
)
|
)
|
||||||
|
|
||||||
func randSeq(n int) string {
|
|
||||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
||||||
letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
|
||||||
b := make([]rune, n)
|
|
||||||
for i := range b {
|
|
||||||
b[i] = letters[r.Intn(len(letters))]
|
|
||||||
}
|
|
||||||
return string(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
reader := bufio.NewReader(os.Stdin)
|
// Wait for the offer to be pasted
|
||||||
rawSd, err := reader.ReadString('\n')
|
sd := mustReadStdin()
|
||||||
if err != nil && err != io.EOF {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("")
|
|
||||||
sd, err := base64.StdEncoding.DecodeString(rawSd)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Everything below is the pion-WebRTC API, thanks for using it! */
|
/* Everything below is the pion-WebRTC API, thanks for using it! */
|
||||||
|
|
||||||
// Create a new RTCPeerConnection
|
// Prepare the configuration
|
||||||
peerConnection, err := webrtc.New(webrtc.RTCConfiguration{
|
config := webrtc.RTCConfiguration{
|
||||||
IceServers: []webrtc.RTCIceServer{
|
IceServers: []webrtc.RTCIceServer{
|
||||||
{
|
{
|
||||||
URLs: []string{"stun:stun.l.google.com:19302"},
|
URLs: []string{"stun:stun.l.google.com:19302"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a new RTCPeerConnection
|
||||||
|
peerConnection, err := webrtc.New(config)
|
||||||
|
check(err)
|
||||||
|
|
||||||
// Set the handler for ICE connection state
|
// Set the handler for ICE connection state
|
||||||
// This will notify you when the peer has connected/disconnected
|
// This will notify you when the peer has connected/disconnected
|
||||||
peerConnection.OnICEConnectionStateChange = func(connectionState ice.ConnectionState) {
|
peerConnection.OnICEConnectionStateChange = func(connectionState ice.ConnectionState) {
|
||||||
fmt.Printf("Connection State has changed %s \n", connectionState.String())
|
fmt.Printf("ICE Connection State has changed: %s\n", connectionState.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
datachannels := make([]*webrtc.RTCDataChannel, 0)
|
// Register data channel creation handling
|
||||||
var dataChannelsLock sync.RWMutex
|
|
||||||
|
|
||||||
peerConnection.OnDataChannel = func(d *webrtc.RTCDataChannel) {
|
peerConnection.OnDataChannel = func(d *webrtc.RTCDataChannel) {
|
||||||
dataChannelsLock.Lock()
|
|
||||||
datachannels = append(datachannels, d)
|
|
||||||
dataChannelsLock.Unlock()
|
|
||||||
|
|
||||||
fmt.Printf("New DataChannel %s %d\n", d.Label, d.ID)
|
fmt.Printf("New DataChannel %s %d\n", d.Label, d.ID)
|
||||||
|
|
||||||
d.Lock()
|
d.Lock()
|
||||||
defer d.Unlock()
|
defer d.Unlock()
|
||||||
|
|
||||||
|
// Register channel opening handling
|
||||||
|
d.OnOpen = func() {
|
||||||
|
fmt.Printf("Data channel '%s'-'%d' open. Random messages will now be sent to any connected DataChannels every 5 seconds\n", d.Label, d.ID)
|
||||||
|
for {
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
message := randSeq(15)
|
||||||
|
fmt.Printf("Sending %s \n", message)
|
||||||
|
|
||||||
|
err := d.Send(datachannel.PayloadString{Data: []byte(message)})
|
||||||
|
check(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register message handling
|
||||||
d.Onmessage = func(payload datachannel.Payload) {
|
d.Onmessage = func(payload datachannel.Payload) {
|
||||||
switch p := payload.(type) {
|
switch p := payload.(type) {
|
||||||
case *datachannel.PayloadString:
|
case *datachannel.PayloadString:
|
||||||
@@ -87,31 +77,50 @@ func main() {
|
|||||||
Type: webrtc.RTCSdpTypeOffer,
|
Type: webrtc.RTCSdpTypeOffer,
|
||||||
Sdp: string(sd),
|
Sdp: string(sd),
|
||||||
}
|
}
|
||||||
if err := peerConnection.SetRemoteDescription(offer); err != nil {
|
|
||||||
panic(err)
|
err = peerConnection.SetRemoteDescription(offer)
|
||||||
}
|
check(err)
|
||||||
|
|
||||||
// Sets the LocalDescription, and starts our UDP listeners
|
// Sets the LocalDescription, and starts our UDP listeners
|
||||||
answer, err := peerConnection.CreateAnswer(nil)
|
answer, err := peerConnection.CreateAnswer(nil)
|
||||||
if err != nil {
|
check(err)
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the LocalDescription and take it to base64 so we can paste in browser
|
// Get the LocalDescription and take it to base64 so we can paste in browser
|
||||||
fmt.Println(base64.StdEncoding.EncodeToString([]byte(answer.Sdp)))
|
fmt.Println(base64.StdEncoding.EncodeToString([]byte(answer.Sdp)))
|
||||||
fmt.Println("Random messages will now be sent to any connected DataChannels every 5 seconds")
|
|
||||||
for {
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
message := randSeq(15)
|
|
||||||
fmt.Printf("Sending %s \n", message)
|
|
||||||
|
|
||||||
dataChannelsLock.RLock()
|
// Block forever
|
||||||
for _, d := range datachannels {
|
select {}
|
||||||
err := d.Send(datachannel.PayloadString{Data: []byte(message)})
|
}
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
func randSeq(n int) string {
|
||||||
}
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
}
|
letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||||
dataChannelsLock.RUnlock()
|
b := make([]rune, n)
|
||||||
|
for i := range b {
|
||||||
|
b[i] = letters[r.Intn(len(letters))]
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mustReadStdin blocks untill input is received from stdin
|
||||||
|
func mustReadStdin() string {
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
rawSd, err := reader.ReadString('\n')
|
||||||
|
if err != io.EOF {
|
||||||
|
check(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("")
|
||||||
|
|
||||||
|
sd, err := base64.StdEncoding.DecodeString(rawSd)
|
||||||
|
check(err)
|
||||||
|
|
||||||
|
return string(sd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check is used to panic in an error occurs.
|
||||||
|
func check(err error) {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -45,13 +45,6 @@ func buildPeerConnection() *webrtc.RTCPeerConnection {
|
|||||||
|
|
||||||
peerConnection.OnICEConnectionStateChange = func(connectionState ice.ConnectionState) {
|
peerConnection.OnICEConnectionStateChange = func(connectionState ice.ConnectionState) {
|
||||||
fmt.Printf("Connection State has changed %s \n", connectionState.String())
|
fmt.Printf("Connection State has changed %s \n", connectionState.String())
|
||||||
if connectionState == ice.ConnectionStateConnected {
|
|
||||||
fmt.Println("sending openchannel")
|
|
||||||
err := d.SendOpenChannelMessage()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("faild to send openchannel", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return peerConnection
|
return peerConnection
|
||||||
|
@@ -59,7 +59,7 @@ func NewManager(btg BufferTransportGenerator, dcet DataChannelEventHandler, ntf
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
m.sctpAssociation = sctp.NewAssocation(m.dataChannelOutboundHandler, m.dataChannelInboundHandler, nil)
|
m.sctpAssociation = sctp.NewAssocation(m.dataChannelOutboundHandler, m.dataChannelInboundHandler, m.handleSCTPState)
|
||||||
|
|
||||||
m.IceAgent = ice.NewAgent(m.iceOutboundHandler, m.iceNotifier)
|
m.IceAgent = ice.NewAgent(m.iceOutboundHandler, m.iceNotifier)
|
||||||
for _, i := range localInterfaces() {
|
for _, i := range localInterfaces() {
|
||||||
@@ -87,6 +87,13 @@ func (m *Manager) handleDTLSState(state dtls.ConnectionState) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) handleSCTPState(state sctp.AssociationState) {
|
||||||
|
if state == sctp.Established {
|
||||||
|
// Temporary way to signal sending OpenChannel messages
|
||||||
|
m.dataChannelEventHandler(&DataChannelOpen{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// AddURL takes an ICE Url, allocates any state and adds the candidate
|
// AddURL takes an ICE Url, allocates any state and adds the candidate
|
||||||
func (m *Manager) AddURL(url *ice.URL) error {
|
func (m *Manager) AddURL(url *ice.URL) error {
|
||||||
|
|
||||||
|
@@ -51,3 +51,11 @@ type DataChannelMessage struct {
|
|||||||
func (d *DataChannelMessage) StreamIdentifier() uint16 {
|
func (d *DataChannelMessage) StreamIdentifier() uint16 {
|
||||||
return d.streamIdentifier
|
return d.streamIdentifier
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DataChannelOpen is emitted when all channels should be opened
|
||||||
|
type DataChannelOpen struct{}
|
||||||
|
|
||||||
|
// StreamIdentifier returns the streamIdentifier
|
||||||
|
func (d *DataChannelOpen) StreamIdentifier() uint16 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
@@ -99,6 +99,10 @@ type RTCDataChannel struct {
|
|||||||
// arrival over the sctp transport from a remote peer.
|
// arrival over the sctp transport from a remote peer.
|
||||||
OnMessage func(datachannel.Payload)
|
OnMessage func(datachannel.Payload)
|
||||||
|
|
||||||
|
// OnOpen designates an event handler which is invoked when
|
||||||
|
// the underlying data transport has been established (or re-established).
|
||||||
|
OnOpen func()
|
||||||
|
|
||||||
// Deprecated: Will be removed when networkManager is deprecated.
|
// Deprecated: Will be removed when networkManager is deprecated.
|
||||||
rtcPeerConnection *RTCPeerConnection
|
rtcPeerConnection *RTCPeerConnection
|
||||||
}
|
}
|
||||||
@@ -122,8 +126,7 @@ type RTCDataChannel struct {
|
|||||||
// return &rtcerr.OperationError{Err: ErrMaxDataChannelID}
|
// return &rtcerr.OperationError{Err: ErrMaxDataChannelID}
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// SendOpenChannelMessage is a test to send OpenChannel manually
|
func (d *RTCDataChannel) sendOpenChannelMessage() error {
|
||||||
func (d *RTCDataChannel) SendOpenChannelMessage() error {
|
|
||||||
if err := d.rtcPeerConnection.networkManager.SendOpenChannelMessage(*d.ID, d.Label); err != nil {
|
if err := d.rtcPeerConnection.networkManager.SendOpenChannelMessage(*d.ID, d.Label); err != nil {
|
||||||
return &rtcerr.UnknownError{Err: err}
|
return &rtcerr.UnknownError{Err: err}
|
||||||
}
|
}
|
||||||
@@ -138,3 +141,12 @@ func (d *RTCDataChannel) Send(p datachannel.Payload) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *RTCDataChannel) doOnOpen() {
|
||||||
|
d.RLock()
|
||||||
|
onOpen := d.OnOpen
|
||||||
|
d.RUnlock()
|
||||||
|
if onOpen != nil {
|
||||||
|
onOpen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -809,10 +809,15 @@ func (pc *RTCPeerConnection) dataChannelEventHandler(e network.DataChannelEvent)
|
|||||||
switch event := e.(type) {
|
switch event := e.(type) {
|
||||||
case *network.DataChannelCreated:
|
case *network.DataChannelCreated:
|
||||||
id := event.StreamIdentifier()
|
id := event.StreamIdentifier()
|
||||||
newDataChannel := &RTCDataChannel{ID: &id, Label: event.Label, rtcPeerConnection: pc}
|
newDataChannel := &RTCDataChannel{ID: &id, Label: event.Label, rtcPeerConnection: pc, ReadyState: RTCDataChannelStateOpen}
|
||||||
pc.dataChannels[e.StreamIdentifier()] = newDataChannel
|
pc.dataChannels[e.StreamIdentifier()] = newDataChannel
|
||||||
if pc.OnDataChannel != nil {
|
if pc.OnDataChannel != nil {
|
||||||
go pc.OnDataChannel(newDataChannel)
|
go func() {
|
||||||
|
pc.OnDataChannel(newDataChannel) // This should actually be called when processing the SDP answer.
|
||||||
|
if newDataChannel.OnOpen != nil {
|
||||||
|
go newDataChannel.doOnOpen()
|
||||||
|
}
|
||||||
|
}()
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("OnDataChannel is unset, discarding message")
|
fmt.Println("OnDataChannel is unset, discarding message")
|
||||||
}
|
}
|
||||||
@@ -830,6 +835,20 @@ func (pc *RTCPeerConnection) dataChannelEventHandler(e network.DataChannelEvent)
|
|||||||
fmt.Printf("No datachannel found for streamIdentifier %d \n", e.StreamIdentifier())
|
fmt.Printf("No datachannel found for streamIdentifier %d \n", e.StreamIdentifier())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
case *network.DataChannelOpen:
|
||||||
|
for _, dc := range pc.dataChannels {
|
||||||
|
dc.Lock()
|
||||||
|
err := dc.sendOpenChannelMessage()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("failed to send openchannel", err)
|
||||||
|
dc.Unlock()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dc.ReadyState = RTCDataChannelStateOpen
|
||||||
|
dc.Unlock()
|
||||||
|
|
||||||
|
go dc.doOnOpen() // TODO: move to ChannelAck handling
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
fmt.Printf("Unhandled DataChannelEvent %v \n", event)
|
fmt.Printf("Unhandled DataChannelEvent %v \n", event)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user