Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,11 @@ The object to pass when creating a new endpoint must refer to the following stru
```
{
id: "<unique ID of the endpoint to create>",
mountpoint: <Streaming mountpoint ID to subscribe to>,
pin: <Streaming mountpoint pin, if required to subscribe (optional)>,
label: <Label to show when subscribing (optional, only relevant in the demo)>,
token: "<token to require via Bearer authorization when using WHIP: can be either a string, or a callback function to validate the provided token (optional)>,
plugin: "<ID of the Janus plugin to subscribe to (optional, default=streaming; supported=videoroom,recordplay)>",
mountpoint: <Plugin resource to subscribe to (e.g., Streaming mountpoint ID)>,
pin: <Resource pin, if required to subscribe (optional)>,
label: "<Label to show when subscribing (optional, only relevant in the demo)>",
token: "<token to require via Bearer authorization when using WHIP: can be either a string, or a callback function to validate the provided token (optional)>",
iceServers: [ array of STUN/TURN servers to return via Link headers (optional, overrides global ones) ]
}
```
Expand Down
10 changes: 8 additions & 2 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,14 @@ This folder contains a few example applications using the Janus WHEP library.

* The `server-shared` folder, instead, contains an example where the application pre-creates a REST server for its own needs, and then tells the library to re-use that server for the WHEP functionality too. A sample endpoint is created, with a callback function used to validate the token any time one is presented.

Both demos subscribe to a few of the events the library can emit for debugging purposes, and serve the `web` folder as static file, which provides a basic WHEP player. Assuming the endpoint `abc123` is available at the WHEP server, you can watch it like that:
* The `videoroom` folder shows how you can configure a WHEP endpoint to rely on the VideoRoom plugin, instead of the Streaming (which is the default), thus simply subscribing to an existing and active publisher in a room.

* The `recordplay` folder shows how you can configure a WHEP endpoint to rely on the RecordPlay plugin, instead of the Streaming (which is the default), thus simply playing an existing Janus recording.

All demos subscribe to a few of the events the library can emit for debugging purposes, and serve the `web` folder as static file, which provides a basic WHEP player. Assuming the endpoint `abc123` is available at the WHEP server, you can watch it like that:

http://localhost:PORT/?id=abc123

where `PORT` is `7190` in the `server-owned` example, and `7090` in the `server-shared` example. Notice that, should you want Janus to send the offer (non-standard WHEP), you can do that by passing an additional `offer=false` to the query string.
where `PORT` is `7190` in the `server-owned`, `videoroom` and `recordplay` examples, and `7090` in the `server-shared` example.

Notice that, should you want Janus to send the offer (non-standard WHEP), you can do that by passing an additional `offer=false` to the query string. This is currently required when trying the `videoroom` or `recordplay` demos, as neither the VideoRoom nor the Record&Play plugins support accepting an offer from subscribers: the only plugin that supports that is the Streaming plugin, which is why it's the default when using the WHEP server library.
29 changes: 29 additions & 0 deletions examples/recordplay/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "janus-whep-recordplay",
"description": "WHEP server where the library uses the Record&Play plugin for subscriptions",
"type": "module",
"keywords": [
"whep",
"wish",
"janus",
"recordplay",
"webrtc",
"meetecho"
],
"author": {
"name": "Lorenzo Miniero",
"email": "[email protected]"
},
"repository": {
"type": "git",
"url": "https://github.com/meetecho/simple-whep-server.git"
},
"license": "ISC",
"private": true,
"main": "src/index.js",
"dependencies": {},
"scripts": {
"build": "npm install --omit=dev",
"start": "node src/index.js"
}
}
53 changes: 53 additions & 0 deletions examples/recordplay/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import express from 'express';
import http from 'http';
import { JanusWhepServer } from '../../../src/whep.js';

(async function main() {
console.log('Example: WHEP server using the Record&Play plugin for subscriptions');
let server = null;

// Create an HTTP server and bind to port 7190 just to list endpoints
let myApp = express();
myApp.get('/endpoints', async (_req, res) => {
res.setHeader('content-type', 'application/json');
res.status(200);
res.send(JSON.stringify(server.listEndpoints()));
});
myApp.get('/subscribers', async (_req, res) => {
res.setHeader('content-type', 'application/json');
res.status(200);
res.send(JSON.stringify(server.listSubscribers()));
});
myApp.use(express.static('../web'));
http.createServer({}, myApp).listen(7190);

// Create a WHEP server, binding to port 7090 and using base path /whep
server = new JanusWhepServer({
janus: {
address: 'ws://localhost:8188'
},
rest: {
port: 7090,
basePath: '/whep'
}
});
// Add a couple of global event handlers
server.on('janus-disconnected', () => {
console.log('WHEP server lost connection to Janus');
});
server.on('janus-reconnected', () => {
console.log('WHEP server reconnected to Janus');
});
// Start the server
await server.start();

// Create a test endpoint using a static token, and pointing to the
// Record&Play recording with ID 1 (assuming it exists)
let endpoint = server.createEndpoint({ id: 'abc123', plugin: 'recordplay', feed: 1234, token: 'verysecret' });
endpoint.on('new-subscriber', function() {
console.log(this.id + ': Endpoint has a new subscriber');
});
endpoint.on('subscriber-gone', function() {
console.log(this.id + ': Endpoint subscriber left');
});
}());
29 changes: 29 additions & 0 deletions examples/videoroom/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "janus-whep-videoroom",
"description": "WHEP server where the library uses the VideoRoom plugin for subscriptions",
"type": "module",
"keywords": [
"whep",
"wish",
"janus",
"videoroom",
"webrtc",
"meetecho"
],
"author": {
"name": "Lorenzo Miniero",
"email": "[email protected]"
},
"repository": {
"type": "git",
"url": "https://github.com/meetecho/simple-whep-server.git"
},
"license": "ISC",
"private": true,
"main": "src/index.js",
"dependencies": {},
"scripts": {
"build": "npm install --omit=dev",
"start": "node src/index.js"
}
}
53 changes: 53 additions & 0 deletions examples/videoroom/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import express from 'express';
import http from 'http';
import { JanusWhepServer } from '../../../src/whep.js';

(async function main() {
console.log('Example: WHEP server using the VideoRoom for subscriptions');
let server = null;

// Create an HTTP server and bind to port 7190 just to list endpoints
let myApp = express();
myApp.get('/endpoints', async (_req, res) => {
res.setHeader('content-type', 'application/json');
res.status(200);
res.send(JSON.stringify(server.listEndpoints()));
});
myApp.get('/subscribers', async (_req, res) => {
res.setHeader('content-type', 'application/json');
res.status(200);
res.send(JSON.stringify(server.listSubscribers()));
});
myApp.use(express.static('../web'));
http.createServer({}, myApp).listen(7190);

// Create a WHEP server, binding to port 7090 and using base path /whep
server = new JanusWhepServer({
janus: {
address: 'ws://localhost:8188'
},
rest: {
port: 7090,
basePath: '/whep'
}
});
// Add a couple of global event handlers
server.on('janus-disconnected', () => {
console.log('WHEP server lost connection to Janus');
});
server.on('janus-reconnected', () => {
console.log('WHEP server reconnected to Janus');
});
// Start the server
await server.start();

// Create a test endpoint using a static token, and pointing to the
// VideoRoom publisher with ID 1 in room 1234 (assuming they exist)
let endpoint = server.createEndpoint({ id: 'abc123', plugin: 'videoroom', room: 1234, feed: 1026326423152807, token: 'verysecret' });
endpoint.on('new-subscriber', function() {
console.log(this.id + ': Endpoint has a new subscriber');
});
endpoint.on('subscriber-gone', function() {
console.log(this.id + ': Endpoint subscriber left');
});
}());
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"dependencies": {
"cors": "^2.8.5",
"express": "^5.1.0",
"janode": "^1.7.4"
"janode": "^1.8.0"
},
"devDependencies": {
"@eslint/js": "^9.4.0",
Expand Down
54 changes: 44 additions & 10 deletions src/whep.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ import cors from 'cors';
import fs from 'fs';
import http from 'http';
import https from 'https';

import Janode from 'janode';
import StreamingPlugin from 'janode/plugins/streaming';
import VideoRoomPlugin from 'janode/plugins/videoroom';
import RecordPlayPlugin from 'janode/plugins/recordplay';
import { EventEmitter } from 'events';

// WHEP server class
Expand Down Expand Up @@ -113,14 +116,22 @@ class JanusWhepServer extends EventEmitter {
return randomString;
}

createEndpoint({ id, mountpoint, pin, label, token, iceServers }) {
if(!id || !mountpoint)
createEndpoint({ id, plugin, mountpoint, room, feed, pin, label, token, iceServers }) {
if(!plugin)
plugin = 'streaming';
if(!id || (plugin === 'streaming' && !mountpoint) ||
(plugin === 'videoroom' && (!room || !feed)) || (plugin === 'recordplay' && !feed))
throw new Error('Invalid arguments');
if(plugin && plugin !== 'streaming' && plugin !== 'videoroom' && plugin !== 'recordplay')
throw new Error('Unsupported plugin');
if(this.endpoints.has(id))
throw new Error('Endpoint already exists');
let endpoint = new JanusWhepEndpoint({
id: id,
plugin: plugin,
mountpoint: mountpoint,
room: room,
feed: feed,
pin: pin,
label: label,
token: token,
Expand Down Expand Up @@ -249,6 +260,11 @@ class JanusWhepServer extends EventEmitter {
}
offer = req.body;
}
if(offer && endpoint.plugin !== 'streaming') {
res.status(406);
res.send('Client offers unsupported by this endpoint');
return;
}
// Check the Bearer token
let auth = req.headers['authorization'];
if(endpoint.token) {
Expand Down Expand Up @@ -286,8 +302,21 @@ class JanusWhepServer extends EventEmitter {
this.logger.info('[' + id + '] Subscribing to WHEP endpoint');
subscriber.enabling = true;
try {
// Connect to the Streaming plugin
subscriber.handle = await this.janus.attach(StreamingPlugin);
// Connect to the specified plugin
let details = {};
if(endpoint.plugin === 'streaming') {
subscriber.handle = await this.janus.attach(StreamingPlugin);
details.id = endpoint.mountpoint;
details.pin = endpoint.pin;
} else if(endpoint.plugin === 'videoroom') {
subscriber.handle = await this.janus.attach(VideoRoomPlugin);
details.room = endpoint.room;
details.feed = endpoint.feed;
details.pin = endpoint.pin;
} else if(endpoint.plugin === 'recordplay') {
subscriber.handle = await this.janus.attach(RecordPlayPlugin);
details.id = endpoint.feed;
}
subscriber.handle.on(Janode.EVENT.HANDLE_DETACHED, () => {
// Janus notified us the session is gone, tear it down
let subscriber = this.subscribers.get(uuid);
Expand All @@ -313,18 +342,20 @@ class JanusWhepServer extends EventEmitter {
this.subscribers.delete(uuid);
}
});
let details = {
id: endpoint.mountpoint,
pin: endpoint.pin
};
if(offer) {
// Client offer (we still support both modes)
details.jsep = {
type: 'offer',
sdp: offer
};
}
const result = await subscriber.handle.watch(details);
let result = null;
if(endpoint.plugin === 'streaming')
result = await subscriber.handle.watch(details);
else if(endpoint.plugin === 'videoroom')
result = await subscriber.handle.joinSubscriber(details);
else if(endpoint.plugin === 'recordplay')
result = await subscriber.handle.play(details);
subscriber.enabling = false;
subscriber.enabled = true;
endpoint.subscribers.set(uuid, true);
Expand Down Expand Up @@ -599,10 +630,13 @@ class JanusWhepServer extends EventEmitter {

// WHEP endpoint class
class JanusWhepEndpoint extends EventEmitter {
constructor({ id, mountpoint, pin, label, token, iceServers }) {
constructor({ id, plugin, mountpoint, room, feed, pin, label, token, iceServers }) {
super();
this.id = id;
this.plugin = plugin;
this.mountpoint = mountpoint;
this.room = room;
this.feed = feed;
this.pin = pin;
this.label = label;
this.token = token;
Expand Down