Add examples/play-from-disk-renegotation

Resolves #207
This commit is contained in:
Sean DuBois
2020-02-09 21:06:40 -08:00
parent 0b8bdebeeb
commit 5f54b68899
4 changed files with 239 additions and 0 deletions

View File

@@ -0,0 +1,29 @@
# play-from-disk-renegotiation
play-from-disk-renegotiation demonstrates Pion WebRTC's renegotiation abilities.
For a simpler example of playing a file from disk we also have [examples/play-from-disk](examples/play-from-disk)
## Instructions
### Download play-from-disk-renegotiation
This example requires you to clone the repo since it is serving static HTML.
```
mkdir -p $GOPATH/src/github.com/pion
cd $GOPATH/src/github.com/pion
git clone https://github.com/pion/webrtc.git
cd webrtc/examples/play-from-disk-renegotiation
```
### Create IVF named `output.ivf` that contains a VP8 track
```
ffmpeg -i $INPUT_FILE -g 30 output.ivf
```
### Run play-from-disk-renegotiation
The `output.ivf` you created should be in the same directory as `play-from-disk-renegotiation`. Execute `go run *.go`
### Open the Web UI
Open [http://localhost:8080](http://localhost:8080) and you should have a `Add Track` and `Remove Track` button. Press these to add as many tracks as you want, or to remove as many as you wish.
Congrats, you have used Pion WebRTC! Now start building something cool

View File

@@ -0,0 +1,71 @@
<html>
<head>
<title>play-from-disk-renegotation</title>
</head>
<body>
<button onclick="window.addVideo()"> Add Video </button><br />
<button onclick="window.removeVideo()"> Remove Video </button><br />
<h3> Video </h3>
<div id="remoteVideos"></div> <br />
<h3> Logs </h3>
<div id="logs"></div>
</body>
<script>
let activeVideos = 0
let pc = new RTCPeerConnection()
pc.ontrack = function (event) {
var el = document.createElement(event.track.kind)
el.srcObject = event.streams[0]
el.autoplay = true
el.controls = true
event.track.onmute = function(event) {
el.parentNode.removeChild(el);
}
document.getElementById('remoteVideos').appendChild(el)
}
let doSignaling = method => {
pc.createOffer()
.then(offer => {
pc.setLocalDescription(offer)
return fetch(`/${method}`, {
method: 'post',
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
body: JSON.stringify(offer)
})
})
.then(res => res.json())
.then(res => pc.setRemoteDescription(res))
.catch(alert)
}
// Create a noop DataChannel. By default PeerConnections do not connect
// if they have no media tracks or DataChannels
pc.createDataChannel('noop')
doSignaling('createPeerConnection')
window.addVideo = () => {
if (pc.getTransceivers().length <= activeVideos) {
pc.addTransceiver('video')
activeVideos++
}
doSignaling('addVideo')
};
window.removeVideo = () => {
doSignaling('removeVideo')
};
</script>
</html>

View File

@@ -0,0 +1,136 @@
package main
import (
"encoding/json"
"fmt"
"math/rand"
"net/http"
"os"
"time"
"github.com/pion/webrtc/v2"
"github.com/pion/webrtc/v2/pkg/media"
"github.com/pion/webrtc/v2/pkg/media/ivfreader"
)
var peerConnection *webrtc.PeerConnection //nolint
// doSignaling exchanges all state of the local PeerConnection and is called
// every time a video is added or removed
func doSignaling(w http.ResponseWriter, r *http.Request) {
var offer webrtc.SessionDescription
if err := json.NewDecoder(r.Body).Decode(&offer); err != nil {
panic(err)
}
if err := peerConnection.SetRemoteDescription(offer); err != nil {
panic(err)
}
answer, err := peerConnection.CreateAnswer(nil)
if err != nil {
panic(err)
} else if err = peerConnection.SetLocalDescription(answer); err != nil {
panic(err)
}
response, err := json.Marshal(answer)
if err != nil {
panic(err)
}
w.Header().Set("Content-Type", "application/json")
if _, err := w.Write(response); err != nil {
panic(err)
}
}
// Add a single video track
func createPeerConnection(w http.ResponseWriter, r *http.Request) {
if peerConnection.ConnectionState() != webrtc.PeerConnectionStateNew {
panic(fmt.Sprintf("createPeerConnection called in non-new state (%s)", peerConnection.ConnectionState()))
}
doSignaling(w, r)
fmt.Println("PeerConnection has been created")
}
// Add a single video track
func addVideo(w http.ResponseWriter, r *http.Request) {
videoTrack, err := peerConnection.NewTrack(webrtc.DefaultPayloadTypeVP8, rand.Uint32(), fmt.Sprintf("video-%d", rand.Uint32()), fmt.Sprintf("video-%d", rand.Uint32()))
if err != nil {
panic(err)
}
if _, err = peerConnection.AddTrack(videoTrack); err != nil {
panic(err)
}
go writeVideoToTrack(videoTrack)
doSignaling(w, r)
fmt.Println("Video track has been added")
}
// Remove a single sender
func removeVideo(w http.ResponseWriter, r *http.Request) {
if senders := peerConnection.GetSenders(); len(senders) != 0 {
if err := peerConnection.RemoveTrack(senders[0]); err != nil {
panic(err)
}
}
doSignaling(w, r)
fmt.Println("Video track has been removed")
}
func main() {
rand.Seed(time.Now().UTC().UnixNano())
var err error
if peerConnection, err = webrtc.NewPeerConnection(webrtc.Configuration{}); 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 webrtc.ICEConnectionState) {
fmt.Printf("ICE Connection State has changed: %s\n", connectionState.String())
})
http.Handle("/", http.FileServer(http.Dir(".")))
http.HandleFunc("/createPeerConnection", createPeerConnection)
http.HandleFunc("/addVideo", addVideo)
http.HandleFunc("/removeVideo", removeVideo)
panic(http.ListenAndServe(":8080", nil))
}
// Read a video file from disk and write it to a webrtc.Track
// When the video has been completely read this exits without error
func writeVideoToTrack(t *webrtc.Track) {
// Open a IVF file and start reading using our IVFReader
file, err := os.Open("output.ivf")
if err != nil {
panic(err)
}
ivf, header, err := ivfreader.NewWith(file)
if err != nil {
panic(err)
}
// Send our video file frame at a time. Pace our sending so we send it at the same speed it should be played back as.
// This isn't required since the video is timestamped, but we will such much higher loss if we send all at once.
sleepTime := time.Millisecond * time.Duration((float32(header.TimebaseNumerator)/float32(header.TimebaseDenominator))*1000)
for {
frame, _, err := ivf.ParseNextFrame()
if err != nil {
fmt.Printf("Finish writing video track: %s ", err)
return
}
time.Sleep(sleepTime)
if err = t.WriteSample(media.Sample{Data: frame, Samples: 90000}); err != nil {
fmt.Printf("Finish writing video track: %s ", err)
return
}
}
}

View File

@@ -71,6 +71,9 @@ func (t *RTPTransceiver) setDirection(d RTPTransceiverDirection) {
func (t *RTPTransceiver) setSendingTrack(track *Track) error {
t.Sender().track = track
if track == nil {
t.setSender(nil)
}
switch {
case track != nil && t.Direction() == RTPTransceiverDirectionRecvonly: