mirror of
https://github.com/pion/webrtc.git
synced 2025-10-06 07:37:10 +08:00
datachannel: send OpenChannel message
This commit is contained in:
31
examples/data-channels-create/README.md
Normal file
31
examples/data-channels-create/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# data-channels
|
||||
data-channels is a pion-WebRTC application that shows how you can send/recv DataChannel messages from a web browser
|
||||
|
||||
## Instructions
|
||||
### Download data-channels
|
||||
```
|
||||
go get github.com/pions/webrtc/examples/data-channels
|
||||
```
|
||||
|
||||
### Open data-channels example page
|
||||
[jsfiddle.net](https://jsfiddle.net/gh/get/library/pure/pions/webrtc/tree/master/examples/data-channels/jsfiddle)
|
||||
|
||||
### Run data-channels, with your browsers SessionDescription as stdin
|
||||
In the jsfiddle the top textarea is your browser's session description, copy that and:
|
||||
#### Linux/macOS
|
||||
Run `echo $BROWSER_SDP | data-channels`
|
||||
#### Windows
|
||||
1. Paste the SessionDescription into a file.
|
||||
1. Run `data-channels < my_file`
|
||||
|
||||
### Input data-channels's SessionDescription into your browser
|
||||
Copy the text that `data-channels` just emitted and copy into second text area
|
||||
|
||||
### Hit 'Start Session' in jsfiddle
|
||||
Under Start Session you should see 'Checking' as it starts connecting. If everything worked you should see `New DataChannel foo 1`
|
||||
|
||||
Now you can put whatever you want in the `Message` textarea, and when you hit `Send Message` it should appear in your browser!
|
||||
|
||||
You can also type in your terminal, and when you hit enter it will appear in your web browser.
|
||||
|
||||
Congrats, you have used pion-WebRTC! Now start building something cool
|
1
examples/data-channels-create/jsfiddle/demo.css
Normal file
1
examples/data-channels-create/jsfiddle/demo.css
Normal file
@@ -0,0 +1 @@
|
||||
|
5
examples/data-channels-create/jsfiddle/demo.details
Normal file
5
examples/data-channels-create/jsfiddle/demo.details
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
name: data-channels
|
||||
description: Example of using pion-WebRTC to communicate with a web browser using bi-direction DataChannels
|
||||
authors:
|
||||
- Sean DuBois
|
9
examples/data-channels-create/jsfiddle/demo.html
Normal file
9
examples/data-channels-create/jsfiddle/demo.html
Normal file
@@ -0,0 +1,9 @@
|
||||
Browser base64 Session Description <textarea id="localSessionDescription" readonly="true"></textarea> <br />
|
||||
Golang base64 Session Description: <textarea id="remoteSessionDescription"></textarea> <br/>
|
||||
<button onclick="window.startSession()"> Start Session </button> <br />
|
||||
<br />
|
||||
|
||||
Message: <textarea id="message">This is my DataChannel message!</textarea> <br/>
|
||||
<button onclick="window.sendMessage()"> Send Message </button> <br />
|
||||
|
||||
<div id="logs"></div>
|
42
examples/data-channels-create/jsfiddle/demo.js
Normal file
42
examples/data-channels-create/jsfiddle/demo.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/* eslint-env browser */
|
||||
|
||||
let pc = new RTCPeerConnection()
|
||||
let log = msg => {
|
||||
document.getElementById('logs').innerHTML += msg + '<br>'
|
||||
}
|
||||
|
||||
let sendChannel = pc.createDataChannel()
|
||||
console.log(sendChannel.id)
|
||||
sendChannel.onclose = () => console.log('sendChannel has closed')
|
||||
sendChannel.onopen = () => console.log('sendChannel has opened')
|
||||
sendChannel.onmessage = e => log(`sendChannel got '${e.data}'`)
|
||||
|
||||
pc.oniceconnectionstatechange = e => log(pc.iceConnectionState)
|
||||
|
||||
pc.onnegotiationneeded = e =>
|
||||
pc.createOffer({ }).then(d => {
|
||||
document.getElementById('localSessionDescription').value = btoa(d.sdp)
|
||||
return pc.setLocalDescription(d)
|
||||
}).catch(log)
|
||||
|
||||
window.sendMessage = () => {
|
||||
let message = document.getElementById('message').value
|
||||
if (message === '') {
|
||||
return alert('Message must not be empty')
|
||||
}
|
||||
|
||||
sendChannel.send(message)
|
||||
}
|
||||
|
||||
window.startSession = () => {
|
||||
let sd = document.getElementById('remoteSessionDescription').value
|
||||
if (sd === '') {
|
||||
return alert('Session Description must not be empty')
|
||||
}
|
||||
|
||||
try {
|
||||
pc.setRemoteDescription(new RTCSessionDescription({type: 'answer', sdp: atob(sd)}))
|
||||
} catch (e) {
|
||||
alert(e)
|
||||
}
|
||||
}
|
110
examples/data-channels-create/main.go
Normal file
110
examples/data-channels-create/main.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/pions/webrtc"
|
||||
"github.com/pions/webrtc/pkg/datachannel"
|
||||
"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() {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
rawSd, err := reader.ReadString('\n')
|
||||
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! */
|
||||
|
||||
// Create a new RTCPeerConnection
|
||||
peerConnection, err := webrtc.New(webrtc.RTCConfiguration{
|
||||
ICEServers: []webrtc.RTCICEServer{
|
||||
{
|
||||
URLs: []string{"stun:stun.l.google.com:19302"},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Set the handler for ICE connection state
|
||||
// This will notify you when the peer has connected/disconnected
|
||||
peerConnection.OnICEConnectionStateChange = func(connectionState ice.ConnectionState) {
|
||||
fmt.Printf("Connection State has changed %s \n", connectionState.String())
|
||||
}
|
||||
|
||||
// TODO: We have to send the offer otherwise it's empty.
|
||||
|
||||
d, err := peerConnection.CreateDataChannel("data", nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("New DataChannel %s %d\n", d.Label, d.ID)
|
||||
|
||||
d.Lock()
|
||||
d.Onmessage = func(payload datachannel.Payload) {
|
||||
switch p := payload.(type) {
|
||||
case *datachannel.PayloadString:
|
||||
fmt.Printf("Message '%s' from DataChannel '%s' payload '%s'\n", p.PayloadType().String(), d.Label, string(p.Data))
|
||||
case *datachannel.PayloadBinary:
|
||||
fmt.Printf("Message '%s' from DataChannel '%s' payload '% 02x'\n", p.PayloadType().String(), d.Label, p.Data)
|
||||
default:
|
||||
fmt.Printf("Message '%s' from DataChannel '%s' no payload \n", p.PayloadType().String(), d.Label)
|
||||
}
|
||||
}
|
||||
d.Unlock()
|
||||
|
||||
// Set the remote SessionDescription
|
||||
offer := webrtc.RTCSessionDescription{
|
||||
Type: webrtc.RTCSdpTypeOffer,
|
||||
Sdp: string(sd),
|
||||
}
|
||||
if err := peerConnection.SetRemoteDescription(offer); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Sets the LocalDescription, and starts our UDP listeners
|
||||
answer, err := peerConnection.CreateAnswer(nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Get the LocalDescription and take it to base64 so we can paste in browser
|
||||
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)
|
||||
|
||||
err := d.Send(datachannel.PayloadString{Data: []byte(message)})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,6 +4,7 @@ import (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"sync"
|
||||
@@ -27,7 +28,7 @@ func randSeq(n int) string {
|
||||
func main() {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
rawSd, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
if err != nil && err != io.EOF {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
|
@@ -270,3 +270,23 @@ func (m *Manager) iceOutboundHandler(raw []byte, local *stun.TransportAddr, remo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) SendOpenChannelMessage(streamIdentifier uint16, label string) error {
|
||||
msg := &datachannel.ChannelOpen{
|
||||
ChannelType: datachannel.ChannelTypeReliable,
|
||||
Priority: datachannel.PriorityNormal,
|
||||
ReliabilityParameter: 0,
|
||||
|
||||
Label: []byte(label),
|
||||
Protocol: []byte(""),
|
||||
}
|
||||
|
||||
rawMsg, err := msg.Marshal()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error Marshaling ChannelOpen %v", err)
|
||||
}
|
||||
if err = m.sctpAssociation.HandleOutbound(rawMsg, streamIdentifier, sctp.PayloadTypeWebRTCDCEP); err != nil {
|
||||
return fmt.Errorf("Error sending ChannelOpen %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
@@ -28,7 +28,7 @@ ChannelOpen represents a DATA_CHANNEL_OPEN Message
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
type ChannelOpen struct {
|
||||
ChannelType byte
|
||||
ChannelType ChannelType
|
||||
Priority uint16
|
||||
ReliabilityParameter uint32
|
||||
|
||||
@@ -40,6 +40,43 @@ const (
|
||||
channelOpenHeaderLength = 12
|
||||
)
|
||||
|
||||
type ChannelType byte
|
||||
|
||||
const (
|
||||
// ChannelTypeReliable determines the Data Channel provides a
|
||||
// reliable in-order bi-directional communication.
|
||||
ChannelTypeReliable = 0x00
|
||||
// ChannelTypeReliableUnordered determines the Data Channel
|
||||
// provides a reliable unordered bi-directional communication.
|
||||
ChannelTypeReliableUnordered = 0x80
|
||||
// ChannelTypePartialReliableRexmit determines the Data Channel
|
||||
// provides a partially-reliable in-order bi-directional communication.
|
||||
// User messages will not be retransmitted more times than specified in the Reliability Parameter.
|
||||
ChannelTypePartialReliableRexmit = 0x01
|
||||
// ChannelTypePartialReliableRexmitUnordered determines
|
||||
// the Data Channel provides a partial reliable unordered bi-directional communication.
|
||||
// User messages will not be retransmitted more times than specified in the Reliability Parameter.
|
||||
ChannelTypePartialReliableRexmitUnordered = 0x81
|
||||
// ChannelTypePartialReliableTimed determines the Data Channel
|
||||
// provides a partial reliable in-order bi-directional communication.
|
||||
// User messages might not be transmitted or retransmitted after
|
||||
// a specified life-time given in milli- seconds in the Reliability Parameter.
|
||||
// This life-time starts when providing the user message to the protocol stack.
|
||||
ChannelTypePartialReliableTimed = 0x02
|
||||
// The Data Channel provides a partial reliable unordered bi-directional
|
||||
// communication. User messages might not be transmitted or retransmitted
|
||||
// after a specified life-time given in milli- seconds in the Reliability Parameter.
|
||||
// This life-time starts when providing the user message to the protocol stack.
|
||||
ChannelTypePartialReliableTimedUnordered = 0x82
|
||||
)
|
||||
|
||||
const (
|
||||
PriorityBelowNormal uint16 = 128
|
||||
PriorityNormal uint16 = 256
|
||||
PriorityHigh uint16 = 512
|
||||
PriorityExtraHigh uint16 = 1024
|
||||
)
|
||||
|
||||
// Marshal returns raw bytes for the given message
|
||||
func (c *ChannelOpen) Marshal() ([]byte, error) {
|
||||
labelLength := len(c.Label)
|
||||
|
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
func TestChannelOpenMarshal(t *testing.T) {
|
||||
msg := ChannelOpen{
|
||||
ChannelType: 0,
|
||||
ChannelType: ChannelTypeReliable,
|
||||
Priority: 0,
|
||||
ReliabilityParameter: 0,
|
||||
|
||||
@@ -18,13 +18,15 @@ func TestChannelOpenMarshal(t *testing.T) {
|
||||
|
||||
rawMsg, err := msg.Marshal()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal: %v", err)
|
||||
t.Errorf("Failed to marshal: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
result := []byte{0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72}
|
||||
|
||||
if len(rawMsg) != len(result) {
|
||||
t.Fatalf("%q != %q", rawMsg, result)
|
||||
t.Errorf("%q != %q", rawMsg, result)
|
||||
return
|
||||
}
|
||||
|
||||
for i, v := range rawMsg {
|
||||
@@ -39,12 +41,14 @@ func TestChannelAckMarshal(t *testing.T) {
|
||||
msg := ChannelAck{}
|
||||
rawMsg, err := msg.Marshal()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal: %v", err)
|
||||
t.Errorf("Failed to marshal: %v", err)
|
||||
return
|
||||
}
|
||||
result := []byte{0x02, 0x00, 0x00, 0x00}
|
||||
|
||||
if len(rawMsg) != len(result) {
|
||||
t.Fatalf("%q != %q", rawMsg, result)
|
||||
t.Errorf("%q != %q", rawMsg, result)
|
||||
return
|
||||
}
|
||||
|
||||
for i, v := range rawMsg {
|
||||
@@ -66,7 +70,7 @@ func TestChannelOpenUnmarshal(t *testing.T) {
|
||||
|
||||
if err != nil {
|
||||
t.Error(errors.Wrap(err, "Unmarshal failed, ChannelOpen"))
|
||||
} else if msg.ChannelType != 0 {
|
||||
} else if msg.ChannelType != ChannelTypeReliable {
|
||||
t.Error(errors.Errorf("ChannelType should be 0"))
|
||||
} else if msg.Priority != 0 {
|
||||
t.Error(errors.Errorf("Priority should be 0"))
|
||||
@@ -83,7 +87,8 @@ func TestChannelAckUnmarshal(t *testing.T) {
|
||||
rawMsg := []byte{0x02}
|
||||
msgUncast, err := Parse(rawMsg)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse: %v", err)
|
||||
t.Errorf("Failed to parse: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
_, ok := msgUncast.(*ChannelAck)
|
||||
|
@@ -51,13 +51,14 @@ func (p RTCPriorityType) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
// RTCDataChannelInit can be used to configure properties of the underlying channel such as data reliability.
|
||||
type RTCDataChannelInit struct {
|
||||
Ordered bool
|
||||
MaxPacketLifeTime *uint16
|
||||
MaxRetransmits *uint16
|
||||
Protocol string
|
||||
Negotiated bool
|
||||
Id uint16
|
||||
ID uint16
|
||||
Priority RTCPriorityType
|
||||
}
|
||||
|
||||
@@ -82,11 +83,15 @@ func (r *RTCPeerConnection) CreateDataChannel(label string, options *RTCDataChan
|
||||
negotiated = options.Negotiated
|
||||
}
|
||||
|
||||
var id uint16 = 0
|
||||
var id uint16
|
||||
if negotiated {
|
||||
id = options.Id
|
||||
id = options.ID
|
||||
} else {
|
||||
// TODO: generate id
|
||||
var err error
|
||||
id, err = r.generateDataChannelID(true) // TODO: base on DTLS role
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if id > 65534 {
|
||||
@@ -98,20 +103,38 @@ func (r *RTCPeerConnection) CreateDataChannel(label string, options *RTCDataChan
|
||||
return nil, &OperationError{Err: ErrMaxDataChannels}
|
||||
}
|
||||
|
||||
// TODO: Actually allocate datachannel
|
||||
_ = ordered // TODO
|
||||
_ = priority // TODO
|
||||
res := &RTCDataChannel{
|
||||
Label: label,
|
||||
ID: id,
|
||||
rtcPeerConnection: r,
|
||||
}
|
||||
|
||||
// TODO handle settings:
|
||||
_ = ordered
|
||||
_ = priority
|
||||
// Remember datachannel
|
||||
r.dataChannels[id] = res
|
||||
|
||||
// Send opening message
|
||||
r.networkManager.SendOpenChannelMessage(id, label)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (r *RTCPeerConnection) generateDataChannelID(client bool) (uint16, error) {
|
||||
var id uint16
|
||||
if !client {
|
||||
id++
|
||||
}
|
||||
|
||||
for ; id < r.sctp.MaxChannels-1; id += 2 {
|
||||
_, ok := r.dataChannels[id]
|
||||
if !ok {
|
||||
return id, nil
|
||||
}
|
||||
}
|
||||
return 0, &OperationError{Err: ErrMaxDataChannels}
|
||||
}
|
||||
|
||||
// Send sends the passed message to the DataChannel peer
|
||||
func (r *RTCDataChannel) Send(p datachannel.Payload) error {
|
||||
if err := r.rtcPeerConnection.networkManager.SendDataChannelMessage(p, r.ID); err != nil {
|
||||
|
35
rtcdatachannel_test.go
Normal file
35
rtcdatachannel_test.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGenerateDataChannelID(t *testing.T) {
|
||||
testCases := []struct {
|
||||
client bool
|
||||
c *RTCPeerConnection
|
||||
result uint16
|
||||
}{
|
||||
{true, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{}}, 0},
|
||||
{true, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{1: nil}}, 0},
|
||||
{true, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{0: nil}}, 2},
|
||||
{true, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{0: nil, 2: nil}}, 4},
|
||||
{true, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{0: nil, 4: nil}}, 2},
|
||||
{false, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{}}, 1},
|
||||
{false, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{0: nil}}, 1},
|
||||
{false, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{1: nil}}, 3},
|
||||
{false, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{1: nil, 3: nil}}, 5},
|
||||
{false, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{1: nil, 5: nil}}, 3},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
id, err := testCase.c.generateDataChannelID(testCase.client)
|
||||
if err != nil {
|
||||
t.Errorf("failed to generate id: %v", err)
|
||||
return
|
||||
}
|
||||
if id != testCase.result {
|
||||
t.Errorf("Wrong id: %d expected %d", id, testCase.result)
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user