Skip to content

Commit b412298

Browse files
authored
[Broadcaster] Port broadcaster to Phoenix (#11)
* Port `broadcaster` to Phoenix * Add ExWebRTC dashboard * Use proper token for WHIP
1 parent 79849c1 commit b412298

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1917
-285
lines changed

broadcaster/.formatter.exs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
# Used by "mix format"
21
[
3-
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
2+
import_deps: [:phoenix],
3+
plugins: [Phoenix.LiveView.HTMLFormatter],
4+
inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}"]
45
]

broadcaster/.gitignore

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
# The directory Mix downloads your dependencies sources to.
88
/deps/
99

10-
# Where third-party dependencies like ExDoc output generated docs.
10+
# Where 3rd-party dependencies like ExDoc output generated docs.
1111
/doc/
1212

1313
# Ignore .fetch files in case you like to edit your project deps locally.
@@ -19,8 +19,19 @@ erl_crash.dump
1919
# Also ignore archive artifacts (built via "mix archive.build").
2020
*.ez
2121

22-
# Ignore package tarball (built via "mix hex.build").
23-
whip_whep_server-*.tar
24-
2522
# Temporary files, for example, from tests.
2623
/tmp/
24+
25+
# Ignore package tarball (built via "mix hex.build").
26+
broadcaster-*.tar
27+
28+
# Ignore assets that are produced by build tools.
29+
/priv/static/assets/
30+
31+
# Ignore digested assets cache.
32+
/priv/static/cache_manifest.json
33+
34+
# In case you use Node.js/npm, you want to ignore these.
35+
npm-debug.log
36+
/assets/node_modules/
37+

broadcaster/README.md

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,24 @@
11
# Broadcaster
22

3-
A [WHIP](https://datatracker.ietf.org/doc/html/draft-ietf-wish-whip-13)/[WHEP](https://datatracker.ietf.org/doc/html/draft-ietf-wish-whep-01) broadcasting server with a simble browser front-end.
3+
A [WHIP](https://datatracker.ietf.org/doc/html/draft-ietf-wish-whip-13)/[WHEP](https://datatracker.ietf.org/doc/html/draft-ietf-wish-whep-01) broadcasting server with a simple browser front-end.
44

55
## Usage
66

77
Clone this repo and change the working directory to `apps/broadcaster`.
88

9-
Fetch dependencies
9+
Fetch dependencies and run the app:
1010

1111
```shell
1212
mix deps.get
13-
```
14-
15-
Set the evironment variables
16-
17-
```shell
18-
# note: these are actually the default values,
19-
# so you can omit setting the variables
20-
export BCR_IP="127.0.0.1"
21-
export BCR_PORT="5002"
22-
export BCR_TOKEN="test"
23-
export BCR_HOST="http://localhost:$BCR_PORT"
24-
export BCR_ADMIN_USERNAME="admin"
25-
export BCR_ADMIN_PASSWORD="admin"
26-
```
27-
28-
Run the app
29-
30-
```shell
31-
mix run --no-halt
13+
mix phx.server
3214
```
3315

3416
We will use [OBS](https://github.com/obsproject/obs-studio) as a media source.
3517
Open OBS an go to `settings > Stream` and change `Service` to `WHIP`.
3618

37-
Pass `$BCR_HOST/api/whip` as the `Server` value and `$BCR_TOKEN` as the `Bearer Token` value, using the environment
19+
Pass `http://localhost:4000/api/whip` as the `Server` value and `example` as the `Bearer Token` value, using the environment
3820
variables values that have been set a moment ago. Press `Apply`.
3921

4022
Close the settings, choose a source of you liking (e.g. a web-cam feed) and press `Start Streaming`.
4123

42-
Acces `$BCR_HOST/` in your browser. You should see the live stream from you OBS.
24+
Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. You should see the stream from OBS.

broadcaster/assets/css/app.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@import "tailwindcss/base";
2+
@import "tailwindcss/components";
3+
@import "tailwindcss/utilities";
4+
5+
/* This file is for your main application CSS */

broadcaster/assets/js/app.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// If you want to use Phoenix channels, run `mix help phx.gen.channel`
2+
// to get started and then uncomment the line below.
3+
// import "./user_socket.js"
4+
5+
// You can include dependencies in two ways.
6+
//
7+
// The simplest option is to put them in assets/vendor and
8+
// import them using relative paths:
9+
//
10+
// import "../vendor/some-package.js"
11+
//
12+
// Alternatively, you can `npm install some-package --prefix assets` and import
13+
// them using a path starting with the package name:
14+
//
15+
// import "some-package"
16+
//
17+
18+
// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
19+
import "phoenix_html"
20+
// Establish Phoenix Socket and LiveView configuration.
21+
import {Socket} from "phoenix"
22+
import {LiveSocket} from "phoenix_live_view"
23+
import topbar from "../vendor/topbar"
24+
25+
import {Home} from "./home.js"
26+
import {Player} from "./player.js"
27+
28+
let Hooks = {}
29+
Hooks.Home = Home
30+
Hooks.Player = Player
31+
32+
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
33+
let liveSocket = new LiveSocket("/live", Socket, {
34+
longPollFallbackMs: 2500,
35+
params: {_csrf_token: csrfToken},
36+
hooks: Hooks,
37+
})
38+
39+
// Show progress bar on live navigation and form submits
40+
topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"})
41+
window.addEventListener("phx:page-loading-start", _info => topbar.show(300))
42+
window.addEventListener("phx:page-loading-stop", _info => topbar.hide())
43+
44+
// connect if there are any LiveViews on the page
45+
liveSocket.connect()
46+
47+
// expose liveSocket on window for web console debug logs and latency simulation:
48+
// >> liveSocket.enableDebug()
49+
// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
50+
// >> liveSocket.disableLatencySim()
51+
window.liveSocket = liveSocket
52+

broadcaster/priv/static/public/script.js renamed to broadcaster/assets/js/home.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
const pcConfig = { iceServers: [{ urls: "stun:stun.l.google.com:19302" }] };
2-
const endpointBase = window.location.href.split("/").slice(0, -1).join("/");
3-
const whepEndpoint = `${endpointBase}/api/whep`;
2+
const whepEndpoint = `${window.location.origin}/api/whep`
43
const videoPlayer = document.getElementById("videoPlayer");
54
const candidates = [];
65
let patchEndpoint;
@@ -74,4 +73,8 @@ async function connect() {
7473
await pc.setRemoteDescription({ type: "answer", sdp: sdp });
7574
}
7675

77-
connect();
76+
export const Home = {
77+
mounted() {
78+
connect()
79+
}
80+
}

broadcaster/priv/static/admin/stream/stream.js renamed to broadcaster/assets/js/player.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,8 @@ async function run() {
122122
bindControls();
123123
}
124124

125-
run();
125+
export const Player = {
126+
mounted() {
127+
run()
128+
}
129+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// See the Tailwind configuration guide for advanced usage
2+
// https://tailwindcss.com/docs/configuration
3+
4+
const plugin = require("tailwindcss/plugin")
5+
const fs = require("fs")
6+
const path = require("path")
7+
8+
module.exports = {
9+
content: [
10+
"./js/**/*.js",
11+
"../lib/broadcaster_web.ex",
12+
"../lib/broadcaster_web/**/*.*ex"
13+
],
14+
theme: {
15+
extend: {
16+
colors: {
17+
brand: "#4339AC",
18+
}
19+
},
20+
},
21+
plugins: [
22+
require("@tailwindcss/forms"),
23+
// Allows prefixing tailwind classes with LiveView classes to add rules
24+
// only when LiveView classes are applied, for example:
25+
//
26+
// <div class="phx-click-loading:animate-ping">
27+
//
28+
plugin(({addVariant}) => addVariant("phx-no-feedback", [".phx-no-feedback&", ".phx-no-feedback &"])),
29+
plugin(({addVariant}) => addVariant("phx-click-loading", [".phx-click-loading&", ".phx-click-loading &"])),
30+
plugin(({addVariant}) => addVariant("phx-submit-loading", [".phx-submit-loading&", ".phx-submit-loading &"])),
31+
plugin(({addVariant}) => addVariant("phx-change-loading", [".phx-change-loading&", ".phx-change-loading &"])),
32+
33+
// Embeds Heroicons (https://heroicons.com) into your app.css bundle
34+
// See your `CoreComponents.icon/1` for more information.
35+
//
36+
plugin(function({matchComponents, theme}) {
37+
let iconsDir = path.join(__dirname, "../deps/heroicons/optimized")
38+
let values = {}
39+
let icons = [
40+
["", "/24/outline"],
41+
["-solid", "/24/solid"],
42+
["-mini", "/20/solid"],
43+
["-micro", "/16/solid"]
44+
]
45+
icons.forEach(([suffix, dir]) => {
46+
fs.readdirSync(path.join(iconsDir, dir)).forEach(file => {
47+
let name = path.basename(file, ".svg") + suffix
48+
values[name] = {name, fullPath: path.join(iconsDir, dir, file)}
49+
})
50+
})
51+
matchComponents({
52+
"hero": ({name, fullPath}) => {
53+
let content = fs.readFileSync(fullPath).toString().replace(/\r?\n|\r/g, "")
54+
let size = theme("spacing.6")
55+
if (name.endsWith("-mini")) {
56+
size = theme("spacing.5")
57+
} else if (name.endsWith("-micro")) {
58+
size = theme("spacing.4")
59+
}
60+
return {
61+
[`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`,
62+
"-webkit-mask": `var(--hero-${name})`,
63+
"mask": `var(--hero-${name})`,
64+
"mask-repeat": "no-repeat",
65+
"background-color": "currentColor",
66+
"vertical-align": "middle",
67+
"display": "inline-block",
68+
"width": size,
69+
"height": size
70+
}
71+
}
72+
}, {values})
73+
})
74+
]
75+
}

0 commit comments

Comments
 (0)