Unverified Commit 4d7c01e6 authored by Leeward Bound's avatar Leeward Bound 💼

feat: play file job repeat, voir admin interface

parent c1745aba
Pipeline #229 passed with stage
in 1 minute and 14 seconds
......@@ -41,6 +41,9 @@ run_second:
docker: protos
docker build . -t ${CI_REGISTRY_IMAGE}:latest
copy_voir:
cp -r ../voir/build/web/* demo/
tag:
test ! -z "$$TAG" && (echo "Tagging $$TAG" \
......
......@@ -3,6 +3,7 @@ package main
import (
"context"
"encoding/json"
"flag"
"fmt"
noir "github.com/net-prophet/noir/pkg/noir"
......@@ -140,7 +141,19 @@ func main() {
worker.RegisterHandler("PlayFile", func(request *pb.NoirRequest) noir.RunnableJob {
admin := request.GetAdmin()
roomAdmin := admin.GetRoomAdmin()
return jobs.NewPlayFileJob(&mgr, roomAdmin.GetRoomID(), "pink.video", true)
options := &jobs.PlayFileOptions{}
packed := roomAdmin.GetRoomJob().GetOptions()
if len(packed) > 0 {
err := json.Unmarshal(packed, options)
if err != nil {
log.Errorf("error unmarshalling job options")
return nil
}
} else {
options.Filename = "pink.video"
options.Repeat = 10
}
return jobs.NewPlayFileJob(&mgr, roomAdmin.GetRoomID(), options.Filename, options.Repeat)
})
go mgr.Noir()
......
{"packages/cupertino_icons/assets/CupertinoIcons.ttf":["packages/cupertino_icons/assets/CupertinoIcons.ttf"],"packages/flutter_icons/fonts/AntDesign.ttf":["packages/flutter_icons/fonts/AntDesign.ttf"],"packages/flutter_icons/fonts/Entypo.ttf":["packages/flutter_icons/fonts/Entypo.ttf"],"packages/flutter_icons/fonts/EvilIcons.ttf":["packages/flutter_icons/fonts/EvilIcons.ttf"],"packages/flutter_icons/fonts/Feather.ttf":["packages/flutter_icons/fonts/Feather.ttf"],"packages/flutter_icons/fonts/FontAwesome.ttf":["packages/flutter_icons/fonts/FontAwesome.ttf"],"packages/flutter_icons/fonts/FontAwesome5_Brands.ttf":["packages/flutter_icons/fonts/FontAwesome5_Brands.ttf"],"packages/flutter_icons/fonts/FontAwesome5_Regular.ttf":["packages/flutter_icons/fonts/FontAwesome5_Regular.ttf"],"packages/flutter_icons/fonts/FontAwesome5_Solid.ttf":["packages/flutter_icons/fonts/FontAwesome5_Solid.ttf"],"packages/flutter_icons/fonts/Foundation.ttf":["packages/flutter_icons/fonts/Foundation.ttf"],"packages/flutter_icons/fonts/Ionicons.ttf":["packages/flutter_icons/fonts/Ionicons.ttf"],"packages/flutter_icons/fonts/MaterialCommunityIcons.ttf":["packages/flutter_icons/fonts/MaterialCommunityIcons.ttf"],"packages/flutter_icons/fonts/MaterialIcons.ttf":["packages/flutter_icons/fonts/MaterialIcons.ttf"],"packages/flutter_icons/fonts/Octicons.ttf":["packages/flutter_icons/fonts/Octicons.ttf"],"packages/flutter_icons/fonts/SimpleLineIcons.ttf":["packages/flutter_icons/fonts/SimpleLineIcons.ttf"],"packages/flutter_icons/fonts/Zocial.ttf":["packages/flutter_icons/fonts/Zocial.ttf"],"packages/flutter_icons/fonts/weathericons.ttf":["packages/flutter_icons/fonts/weathericons.ttf"]}
\ No newline at end of file
[{"family":"MaterialIcons","fonts":[{"asset":"fonts/MaterialIcons-Regular.otf"}]},{"family":"packages/cupertino_icons/CupertinoIcons","fonts":[{"asset":"packages/cupertino_icons/assets/CupertinoIcons.ttf"}]},{"family":"packages/flutter_icons/Ionicons","fonts":[{"asset":"packages/flutter_icons/fonts/Ionicons.ttf"}]},{"family":"packages/flutter_icons/AntDesign","fonts":[{"asset":"packages/flutter_icons/fonts/AntDesign.ttf"}]},{"family":"packages/flutter_icons/FontAwesome","fonts":[{"asset":"packages/flutter_icons/fonts/FontAwesome.ttf"}]},{"family":"packages/flutter_icons/MaterialIcons","fonts":[{"asset":"packages/flutter_icons/fonts/MaterialIcons.ttf"}]},{"family":"packages/flutter_icons/Entypo","fonts":[{"asset":"packages/flutter_icons/fonts/Entypo.ttf"}]},{"family":"packages/flutter_icons/EvilIcons","fonts":[{"asset":"packages/flutter_icons/fonts/EvilIcons.ttf"}]},{"family":"packages/flutter_icons/Feather","fonts":[{"asset":"packages/flutter_icons/fonts/Feather.ttf"}]},{"family":"packages/flutter_icons/Foundation","fonts":[{"asset":"packages/flutter_icons/fonts/Foundation.ttf"}]},{"family":"packages/flutter_icons/MaterialCommunityIcons","fonts":[{"asset":"packages/flutter_icons/fonts/MaterialCommunityIcons.ttf"}]},{"family":"packages/flutter_icons/Octicons","fonts":[{"asset":"packages/flutter_icons/fonts/Octicons.ttf"}]},{"family":"packages/flutter_icons/SimpleLineIcons","fonts":[{"asset":"packages/flutter_icons/fonts/SimpleLineIcons.ttf"}]},{"family":"packages/flutter_icons/Zocial","fonts":[{"asset":"packages/flutter_icons/fonts/Zocial.ttf"}]},{"family":"packages/flutter_icons/FontAwesome5","fonts":[{"asset":"packages/flutter_icons/fonts/FontAwesome5_Regular.ttf"}]},{"family":"packages/flutter_icons/FontAwesome5_Brands","fonts":[{"asset":"packages/flutter_icons/fonts/FontAwesome5_Brands.ttf"}]},{"family":"packages/flutter_icons/FontAwesome5_Solid","fonts":[{"asset":"packages/flutter_icons/fonts/FontAwesome5_Solid.ttf"}]},{"family":"packages/flutter_icons/WeatherIcons","fonts":[{"asset":"packages/flutter_icons/fonts/weathericons.ttf"}]}]
\ No newline at end of file
This diff is collapsed.
'use strict';
const MANIFEST = 'flutter-app-manifest';
const TEMP = 'flutter-temp-cache';
const CACHE_NAME = 'flutter-app-cache';
const RESOURCES = {
"icons/Icon-512.png": "96e752610906ba2a93c65f8abe1645f1",
"icons/Icon-192.png": "ac9a721a12bbc803b44f645561ecb1e1",
"favicon.png": "5dcef449791fa27946b3d35ad8803796",
"manifest.json": "15f73b7e8a8209c2206210b3ac8dea1b",
"index.html": "ea2654a09da299e52ceddcee7cff1685",
"/": "ea2654a09da299e52ceddcee7cff1685",
"main.dart.js": "4f7e140d986be342bdec2b5b89b325c7",
"assets/packages/cupertino_icons/assets/CupertinoIcons.ttf": "6d342eb68f170c97609e9da345464e5e",
"assets/packages/flutter_icons/fonts/Zocial.ttf": "5cdf883b18a5651a29a4d1ef276d2457",
"assets/packages/flutter_icons/fonts/Octicons.ttf": "73b8cff012825060b308d2162f31dbb2",
"assets/packages/flutter_icons/fonts/FontAwesome.ttf": "b06871f281fee6b241d60582ae9369b9",
"assets/packages/flutter_icons/fonts/Entypo.ttf": "744ce60078c17d86006dd0edabcd59a7",
"assets/packages/flutter_icons/fonts/AntDesign.ttf": "3a2ba31570920eeb9b1d217cabe58315",
"assets/packages/flutter_icons/fonts/MaterialIcons.ttf": "a37b0c01c0baf1888ca812cc0508f6e2",
"assets/packages/flutter_icons/fonts/SimpleLineIcons.ttf": "d2285965fe34b05465047401b8595dd0",
"assets/packages/flutter_icons/fonts/Feather.ttf": "6beba7e6834963f7f171d3bdd075c915",
"assets/packages/flutter_icons/fonts/Foundation.ttf": "e20945d7c929279ef7a6f1db184a4470",
"assets/packages/flutter_icons/fonts/FontAwesome5_Solid.ttf": "b70cea0339374107969eb53e5b1f603f",
"assets/packages/flutter_icons/fonts/MaterialCommunityIcons.ttf": "3c851d60ad5ef3f2fe43ebd263490d78",
"assets/packages/flutter_icons/fonts/FontAwesome5_Brands.ttf": "c39278f7abfc798a241551194f55e29f",
"assets/packages/flutter_icons/fonts/FontAwesome5_Regular.ttf": "f6c6f6c8cb7784254ad00056f6fbd74e",
"assets/packages/flutter_icons/fonts/weathericons.ttf": "4618f0de2a818e7ad3fe880e0b74d04a",
"assets/packages/flutter_icons/fonts/Ionicons.ttf": "b2e0fc821c6886fb3940f85a3320003e",
"assets/packages/flutter_icons/fonts/EvilIcons.ttf": "140c53a7643ea949007aa9a282153849",
"assets/AssetManifest.json": "f8fc9e1fa5a3fb873ef5566e8c8d9b59",
"assets/fonts/MaterialIcons-Regular.otf": "1288c9e28052e028aba623321f7826ac",
"assets/NOTICES": "831ad35d3b80de861888d0d57df73570",
"assets/FontManifest.json": "7aaf3996738086bbd796613e14ef9e45",
"version.json": "7117fe218bfd641c08379b9ba9f9b7c5"
};
// The application shell files that are downloaded before a service worker can
// start.
const CORE = [
"/",
"main.dart.js",
"index.html",
"assets/NOTICES",
"assets/AssetManifest.json",
"assets/FontManifest.json"];
// During install, the TEMP cache is populated with the application shell files.
self.addEventListener("install", (event) => {
self.skipWaiting();
return event.waitUntil(
caches.open(TEMP).then((cache) => {
return cache.addAll(
CORE.map((value) => new Request(value + '?revision=' + RESOURCES[value], {'cache': 'reload'})));
})
);
});
// During activate, the cache is populated with the temp files downloaded in
// install. If this service worker is upgrading from one with a saved
// MANIFEST, then use this to retain unchanged resource files.
self.addEventListener("activate", function(event) {
return event.waitUntil(async function() {
try {
var contentCache = await caches.open(CACHE_NAME);
var tempCache = await caches.open(TEMP);
var manifestCache = await caches.open(MANIFEST);
var manifest = await manifestCache.match('manifest');
// When there is no prior manifest, clear the entire cache.
if (!manifest) {
await caches.delete(CACHE_NAME);
contentCache = await caches.open(CACHE_NAME);
for (var request of await tempCache.keys()) {
var response = await tempCache.match(request);
await contentCache.put(request, response);
}
await caches.delete(TEMP);
// Save the manifest to make future upgrades efficient.
await manifestCache.put('manifest', new Response(JSON.stringify(RESOURCES)));
return;
}
var oldManifest = await manifest.json();
var origin = self.location.origin;
for (var request of await contentCache.keys()) {
var key = request.url.substring(origin.length + 1);
if (key == "") {
key = "/";
}
// If a resource from the old manifest is not in the new cache, or if
// the MD5 sum has changed, delete it. Otherwise the resource is left
// in the cache and can be reused by the new service worker.
if (!RESOURCES[key] || RESOURCES[key] != oldManifest[key]) {
await contentCache.delete(request);
}
}
// Populate the cache with the app shell TEMP files, potentially overwriting
// cache files preserved above.
for (var request of await tempCache.keys()) {
var response = await tempCache.match(request);
await contentCache.put(request, response);
}
await caches.delete(TEMP);
// Save the manifest to make future upgrades efficient.
await manifestCache.put('manifest', new Response(JSON.stringify(RESOURCES)));
return;
} catch (err) {
// On an unhandled exception the state of the cache cannot be guaranteed.
console.error('Failed to upgrade service worker: ' + err);
await caches.delete(CACHE_NAME);
await caches.delete(TEMP);
await caches.delete(MANIFEST);
}
}());
});
// The fetch handler redirects requests for RESOURCE files to the service
// worker cache.
self.addEventListener("fetch", (event) => {
if (event.request.method !== 'GET') {
return;
}
var origin = self.location.origin;
var key = event.request.url.substring(origin.length + 1);
// Redirect URLs to the index.html
if (key.indexOf('?v=') != -1) {
key = key.split('?v=')[0];
}
if (event.request.url == origin || event.request.url.startsWith(origin + '/#') || key == '') {
key = '/';
}
// If the URL is not the RESOURCE list then return to signal that the
// browser should take over.
if (!RESOURCES[key]) {
return;
}
// If the URL is the index.html, perform an online-first request.
if (key == '/') {
return onlineFirst(event);
}
event.respondWith(caches.open(CACHE_NAME)
.then((cache) => {
return cache.match(event.request).then((response) => {
// Either respond with the cached resource, or perform a fetch and
// lazily populate the cache.
return response || fetch(event.request).then((response) => {
cache.put(event.request, response.clone());
return response;
});
})
})
);
});
self.addEventListener('message', (event) => {
// SkipWaiting can be used to immediately activate a waiting service worker.
// This will also require a page refresh triggered by the main worker.
if (event.data === 'skipWaiting') {
self.skipWaiting();
return;
}
if (event.data === 'downloadOffline') {
downloadOffline();
return;
}
});
// Download offline will check the RESOURCES for all files not in the cache
// and populate them.
async function downloadOffline() {
var resources = [];
var contentCache = await caches.open(CACHE_NAME);
var currentContent = {};
for (var request of await contentCache.keys()) {
var key = request.url.substring(origin.length + 1);
if (key == "") {
key = "/";
}
currentContent[key] = true;
}
for (var resourceKey in Object.keys(RESOURCES)) {
if (!currentContent[resourceKey]) {
resources.push(resourceKey);
}
}
return contentCache.addAll(resources);
}
// Attempt to download the resource online before falling back to
// the offline cache.
function onlineFirst(event) {
return event.respondWith(
fetch(event.request).then((response) => {
return caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, response.clone());
return response;
});
}).catch((error) => {
return caches.open(CACHE_NAME).then((cache) => {
return cache.match(event.request).then((response) => {
if (response != null) {
return response;
}
throw error;
});
});
})
);
}
<!DOCTYPE html>
<html>
<head>
<style>
body,
html {
margin: 0;
padding: 0;
height: 100vh;
width: 100vw;
background: black;
color: white;
text-align: center;
}
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
li {
font-size: 1.2em;
display: inline-block;
}
a {
color: white;
}
</style>
</head>
<body>
<h1>noiR</h1>
<li><a href="/storybook">storybook</a></li>
<li><a href="/echotest">echotest</a></li>
<li><a href="/pubsubtest">pubsubtest</a></li>
<li><a href="/gallerytest">gallerytest</a></li>
</body>
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
Fore more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
-->
<base href="/">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="example">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<title>example</title>
<link rel="manifest" href="manifest.json">
</head>
<body>
<!-- This script installs service_worker.js to provide PWA functionality to
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('flutter-first-frame', function () {
navigator.serviceWorker.register('flutter_service_worker.js?v=1283165716');
});
}
</script>
<script src="main.dart.js" type="application/javascript"></script>
</body>
</html>
This diff is collapsed.
{
"name": "example",
"short_name": "example",
"start_url": ".",
"display": "standalone",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "A new Flutter project.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/Icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
{"app_name":"voir","version":"1.0.0","build_number":"1"}
\ No newline at end of file
7605e9ca3d9aa17c9701f70a04736d74
\ No newline at end of file
......@@ -39,12 +39,12 @@ func NewBaseJob(manager *Manager, handler string, jobID string) *Job {
}
}
func NewPeerJob(manager *Manager, handler string, roomID string, peerID string) *PeerJob {
func NewPeerJob(manager *Manager, handler string, roomID string, userID string) *PeerJob {
return &PeerJob{
Job: *NewBaseJob(manager, handler, "job-"+peerID),
Job: *NewBaseJob(manager, handler, "job-"+userID),
peerJobData: &pb.PeerJobData{
RoomID: roomID,
PeerID: peerID,
UserID: "job-"+userID,
PublishTracks: []string{},
SubscribeTracks: []string{},
},
......@@ -57,7 +57,7 @@ func (j *Job) GetCommandQueue() Queue {
}
func (j *PeerJob) GetPeerQueue() Queue {
return j.manager.GetQueue(pb.KeyTopicFromPeer(j.peerJobData.GetPeerID()))
return j.manager.GetQueue(pb.KeyTopicFromPeer(j.peerJobData.GetUserID()))
}
func (j *Job) GetManager() *Manager {
......@@ -71,19 +71,76 @@ func (j *Job) GetData() *pb.JobData {
func (j *Job) Kill(code int) {
log.Infof("exited %s handler=%s jobid=%s ", code, j.jobData.GetHandler(), j.id)
}
func (j *PeerJob) Kill(code int) {
log.Infof("exited %s handler=%s jobid=%s userid=%s", code, j.jobData.GetHandler(), j.id, j.peerJobData.UserID)
j.manager.DisconnectUser(j.peerJobData.UserID)
if j.pc != nil {
j.pc.Close()
}
}
func (j *Job) KillWithError(err error) {
log.Errorf("job error: %s", err)
j.Kill(1)
}
func (j *PeerJob) KillWithError(err error) {
log.Errorf("job error: %s", err)
j.Kill(1)
}
func (j *PeerJob) GetPeerData() *pb.PeerJobData {
return j.peerJobData
}
func (j *PeerJob) GetPeerConnection() *webrtc.PeerConnection {
return j.pc
func (j *PeerJob) GetPeerConnection() (*webrtc.PeerConnection, error) {
if j.pc == nil {
mediaEngine := webrtc.MediaEngine{}
mediaEngine.RegisterDefaultCodecs()
// Create a new RTCPeerConnection
api := webrtc.NewAPI(webrtc.WithMediaEngine(&mediaEngine))
pc, err := api.NewPeerConnection(webrtc.Configuration{
ICEServers: []webrtc.ICEServer{
{
URLs: []string{"stun:stun.l.google.com:19302"},
},
},
})
if err != nil {
log.Errorf("error getting pc %s", err)
return nil, err
}
j.pc = pc
}
return j.pc, nil
}
func (j *PeerJob) SendJoin() error {
router := j.GetManager().GetRouter()
queue := (*router).GetQueue()
userID := j.GetPeerData().UserID
roomID := j.GetPeerData().RoomID
pc, err := j.GetPeerConnection()
if err != nil {
return err
}
log.Infof("joining room=%s user=%s", roomID, userID)
return EnqueueRequest(*queue, &pb.NoirRequest{
Command: &pb.NoirRequest_Signal{
Signal: &pb.SignalRequest{
Id: userID,
Payload: &pb.SignalRequest_Join{
Join: &pb.JoinRequest{
Sid: roomID,
Description: []byte(pc.LocalDescription().SDP),
},
},
},
},
})
}
func (j *PeerJob) GetFromPeerQueue() Queue {
return j.manager.GetQueue(pb.KeyTopicFromPeer(j.peerJobData.PeerID))
return j.manager.GetQueue(pb.KeyTopicFromPeer(j.peerJobData.UserID))
}
......@@ -19,7 +19,7 @@ import (
type PlayFileOptions struct {
Filename string `json:"filename"`
Repeat bool `json:"repeat"`
Repeat int `json:"repeat"`
}
type PlayFileJob struct {
......@@ -29,7 +29,7 @@ type PlayFileJob struct {
const handler = "PlayFile"
func NewPlayFileJob(manager *noir.Manager, roomID string, filename string, repeat bool) *PlayFileJob {
func NewPlayFileJob(manager *noir.Manager, roomID string, filename string, repeat int) *PlayFileJob {
return &PlayFileJob{
PeerJob: *noir.NewPeerJob(manager, handler, roomID, noir.RandomString(16)),
options: &PlayFileOptions{Filename: filename, Repeat: repeat},
......@@ -46,20 +46,8 @@ func (j *PlayFileJob) Handle() {
panic("Could not find `" + filename + "`")
}
// We make our own mediaEngine so we can place the sender's codecs in it. This because we must use the
// dynamic media type from the sender in our answer. This is not required if we are the offerer
mediaEngine := webrtc.MediaEngine{}
mediaEngine.RegisterDefaultCodecs()
// Create a new RTCPeerConnection
api := webrtc.NewAPI(webrtc.WithMediaEngine(&mediaEngine))
peerConnection, err := api.NewPeerConnection(webrtc.Configuration{
ICEServers: []webrtc.ICEServer{
{
URLs: []string{"stun:stun.l.google.com:19302"},
},
},
})
peerConnection, err := j.GetPeerConnection()
if err != nil {
j.KillWithError(err)
return
......@@ -100,15 +88,33 @@ func (j *PlayFileJob) Handle() {
<-iceConnectedCtx.Done()
log.Infof("done waiting for connection...")
// A positive repeat will play the file N times, a negative repeat will loop forever
repeat := j.options.Repeat
// 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, _, ivfErr := ivf.ParseNextFrame()
if ivfErr == io.EOF {
fmt.Printf("All video frames parsed and sent")
j.Kill(0)
return
if repeat == -1 || repeat > 0 {
file.Seek(0, 0)
ivf, header, ivfErr = ivfreader.NewWith(file)
frame, _, ivfErr = ivf.ParseNextFrame()
if ivfErr != nil {
j.KillWithError(ivfErr)
return
}
if repeat > 0 {
log.Debugf("repeating %s %d more times", filename, repeat)
repeat = repeat - 1
}
} else {
fmt.Printf("All video frames parsed and sent")
j.Kill(0)
return
}
}
if ivfErr != nil {
......@@ -147,30 +153,13 @@ func (j *PlayFileJob) Handle() {
<-gatherComplete
err = j.SendJoin()
if err != nil {
log.Errorf("Error publishing stream: %v", err)
j.KillWithError(err)
}
router := j.GetManager().GetRouter()
queue := (*router).GetQueue()
peerID := j.GetPeerData().PeerID
roomID := j.GetPeerData().RoomID
log.Infof("joining room=%s peer=%s", roomID, peerID)
err = noir.EnqueueRequest(*queue, &pb.NoirRequest{
Command: &pb.NoirRequest_Signal{
Signal: &pb.SignalRequest{
Id: peerID,
Payload: &pb.SignalRequest_Join{
Join: &pb.JoinRequest{
Sid: roomID,
Description: []byte(peerConnection.LocalDescription().SDP),
},
},
},
},
})
if err != nil {
log.Errorf("Error sending publish request: %v", err)
j.KillWithError(err)
......@@ -198,7 +187,7 @@ func (j *PlayFileJob) Handle() {
if signal, ok := reply.Command.(*pb.NoirReply_Signal); ok {
if join := signal.Signal.GetJoin() ; join != nil {
log.Debugf("playfile connected %s => %s!\n", peerID)
log.Debugf("playfile connected %s => %s!\n", signal.Signal.Id)
// Set the remote SessionDescription
desc := &webrtc.SessionDescription{}
json.Unmarshal(join.Description, desc)
......@@ -208,7 +197,7 @@ func (j *PlayFileJob) Handle() {
}
}
if signal.Signal.GetKill() {
log.Debugf("signal killed room=%s peer=%s", roomID, peerID)
log.Debugf("signal killed user=%s", signal.Signal.Id)
j.Kill(0)
return
}
......
......@@ -63,9 +63,14 @@ func (w *worker) HandleAdmin(request *pb.NoirRequest) error {
Id: z.Member.(string),