From e7efd64ba4f3d2076ae93870ba76efccebb85c85 Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Fri, 24 Apr 2020 11:27:08 +0200 Subject: [PATCH 1/5] Get TURN config from shittyserver instead of hard-coding --- requirements.txt | 4 ++ serverconfig.ini.example | 10 ++++- shittyserver.py | 75 ++++++++++++++++++++++++++--------- src/broadcast/rtc_streamer.ts | 72 +++++++++++++++------------------ 4 files changed, 100 insertions(+), 61 deletions(-) diff --git a/requirements.txt b/requirements.txt index 232be3b..80ef696 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,14 +29,18 @@ parso==0.6.2 pluggy==0.13.1 pycparser==2.20 pyee==7.0.1 +PyJWT==1.7.1 pylibsrtp==0.6.6 pyls==0.1.6 pyls-mypy==0.1.8 +python-dateutil==2.8.1 python-jsonrpc-server==0.3.4 python-language-server==0.31.9 +pytz==2019.3 raygun4py==4.3.0 requests==2.23.0 six==1.14.0 +twilio==6.38.1 typed-ast==1.4.1 typing-extensions==3.7.4.2 ujson==1.35 diff --git a/serverconfig.ini.example b/serverconfig.ini.example index d3a2ce3..5abfed2 100644 --- a/serverconfig.ini.example +++ b/serverconfig.ini.example @@ -2,11 +2,19 @@ key = CHANGEME enable = False +[twilio] +account_sid = CHANGEME +auth_token = CHANGEME + +[time] +local_timezone = Europe/London + [shittyserver] notify_url = https://example.com websocket_port = 8079 telnet_port = 8078 +turn_provider = twilio [stateserver] myradio_key = CHANGEME -sustainer_autonews = True \ No newline at end of file +sustainer_autonews = True diff --git a/shittyserver.py b/shittyserver.py index 03acf6f..15834da 100644 --- a/shittyserver.py +++ b/shittyserver.py @@ -7,7 +7,7 @@ import sys import uuid from datetime import datetime, timezone from types import TracebackType -from typing import Optional, Any, Type, Dict +from typing import Optional, Any, Type, Dict, List import aiohttp import av # type: ignore @@ -21,18 +21,17 @@ config = configparser.RawConfigParser() config.read("serverconfig.ini") if config.get("raygun", "enable") == "True": - def handle_exception( - exc_type: Type[BaseException], - exc_value: BaseException, - exc_traceback: TracebackType, + exc_type: Type[BaseException], + exc_value: BaseException, + exc_traceback: TracebackType, ) -> None: sys.__excepthook__(exc_type, exc_value, exc_traceback) cl = raygunprovider.RaygunSender(config.get("raygun", "key")) cl.send_exception(exc_info=(exc_type, exc_value, exc_traceback)) - sys.excepthook = handle_exception + sys.excepthook = handle_exception file_contents_ex = re.compile(r"^ws=\d$") @@ -54,6 +53,42 @@ def write_ob_status(status: bool) -> None: fd.truncate() +TurnCredentials = List[Dict[str, Any]] + + +def get_turn_credentials() -> TurnCredentials: + provider = config.get("shittyserver", "turn_provider") + if provider == "twilio": + from twilio.rest import Client # type: ignore + client = Client(config.get("twilio", "account_sid"), config.get("twilio", "auth_token")) + + token = client.tokens.create() + # Twilio's typedef is wrong, reee + # noinspection PyTypeChecker + return token.ice_servers # type: ignore + elif provider == "hardcoded": + return [ + { + "urls": ["stun:eu-turn4.xirsys.com"], + }, + { + "username": + "h42bRBHL2GtRTiQRoXN8GCG-PFYMl4Acel6EQ9xINBWdTpoZyBEGyCcJBCtT3iINAAAAAF5_NJptYXJrc3BvbGFrb3Zz", + "credential": "17e834fa-70e7-11ea-a66c-faa4ea02ad5c", + "urls": [ + "turn:eu-turn4.xirsys.com:80?transport=udp", + "turn:eu-turn4.xirsys.com:3478?transport=udp", + "turn:eu-turn4.xirsys.com:80?transport=tcp", + "turn:eu-turn4.xirsys.com:3478?transport=tcp", + "turns:eu-turn4.xirsys.com:443?transport=tcp", + "turns:eu-turn4.xirsys.com:5349?transport=tcp", + ], + }, + ] + else: + raise Exception("unknown provider " + provider) + + @Jack.set_error_function # type: ignore def error(msg: str) -> None: print("Error:", msg) @@ -177,8 +212,8 @@ class Session(object): await self.pc.close() if ( - self.websocket is not None - and self.websocket.state == websockets.protocol.State.OPEN + self.websocket is not None + and self.websocket.state == websockets.protocol.State.OPEN ): try: await self.websocket.send(json.dumps({"kind": "DIED"})) @@ -311,9 +346,11 @@ class Session(object): self.websocket = websocket self.connection_state = "HELLO" print(self.connection_id, "Connected") + ice_config = get_turn_credentials() + print(self.connection_id, "Obtained ICE") # TODO Raygun user ID await websocket.send( - json.dumps({"kind": "HELLO", "connectionId": self.connection_id}) + json.dumps({"kind": "HELLO", "connectionId": self.connection_id, "iceServers": ice_config}) ) try: @@ -353,7 +390,7 @@ print("Shittyserver WS starting on port {}.".format(config.get("shittyserver", " async def telnet_server( - reader: asyncio.StreamReader, writer: asyncio.StreamWriter + reader: asyncio.StreamReader, writer: asyncio.StreamWriter ) -> None: global active_sessions, live_session while True: @@ -374,15 +411,15 @@ async def telnet_server( result[sid] = sess.to_dict() writer.write( ( - json.dumps( - { - "live": live_session.to_dict() - if live_session is not None - else None, - "active": result, - } - ) - + "\r\n" + json.dumps( + { + "live": live_session.to_dict() + if live_session is not None + else None, + "active": result, + } + ) + + "\r\n" ).encode("utf-8") ) diff --git a/src/broadcast/rtc_streamer.ts b/src/broadcast/rtc_streamer.ts index 45b1e40..480146d 100644 --- a/src/broadcast/rtc_streamer.ts +++ b/src/broadcast/rtc_streamer.ts @@ -29,46 +29,6 @@ export class WebRTCStreamer extends Streamer { async start(): Promise { console.log("RTCStreamer start"); - this.pc = new RTCPeerConnection({ - iceServers: [ - { - urls: [ - "stun:stun.l.google.com:19302", - "stun:stun1.l.google.com:19302", - "stun:stun2.l.google.com:19302", - "stun:stun3.l.google.com:19302", - "stun:stun4.l.google.com:19302", - ], - }, - { - urls: ["stun:eu-turn4.xirsys.com"], - }, - { - username: - "h42bRBHL2GtRTiQRoXN8GCG-PFYMl4Acel6EQ9xINBWdTpoZyBEGyCcJBCtT3iINAAAAAF5_NJptYXJrc3BvbGFrb3Zz", - credential: "17e834fa-70e7-11ea-a66c-faa4ea02ad5c", - urls: [ - "turn:eu-turn4.xirsys.com:80?transport=udp", - "turn:eu-turn4.xirsys.com:3478?transport=udp", - "turn:eu-turn4.xirsys.com:80?transport=tcp", - "turn:eu-turn4.xirsys.com:3478?transport=tcp", - "turns:eu-turn4.xirsys.com:443?transport=tcp", - "turns:eu-turn4.xirsys.com:5349?transport=tcp", - ], - }, - ], - }); - this.pc.oniceconnectionstatechange = (e) => { - if (!this.pc) { - throw new Error( - "Received ICEConnectionStateChange but PC was null?????" - ); - } - console.log("ICE Connection state change: " + this.pc.iceConnectionState); - this.onStateChange(this.mapStateToConnectionState()); - }; - this.stream.getAudioTracks().forEach((track) => this.pc!.addTrack(track)); - this.addConnectionStateListener((state) => { if (state === "CONNECTED") { this.newsInterval = later.setInterval( @@ -83,7 +43,6 @@ export class WebRTCStreamer extends Streamer { } }); - console.log("PC created"); this.ws = new WebSocket(process.env.REACT_APP_WS_URL!); this.ws.onopen = (e) => { console.log("WS open"); @@ -156,6 +115,9 @@ export class WebRTCStreamer extends Streamer { if (this.state !== "HELLO") { this.ws!.close(); } + + this.createPeerConnection(data.iceServers); + if (!this.pc) { throw new Error( "Tried to do websocket fuckery with a null PeerConnection!" @@ -225,6 +187,34 @@ export class WebRTCStreamer extends Streamer { } } + createPeerConnection(iceServers: RTCIceServer[]) { + this.pc = new RTCPeerConnection({ + iceServers: [ + { + urls: [ + "stun:stun.l.google.com:19302", + "stun:stun1.l.google.com:19302", + "stun:stun2.l.google.com:19302", + "stun:stun3.l.google.com:19302", + "stun:stun4.l.google.com:19302", + ], + }, + ...iceServers, + ], + }); + this.pc.oniceconnectionstatechange = (e) => { + if (!this.pc) { + throw new Error( + "Received ICEConnectionStateChange but PC was null?????" + ); + } + console.log("ICE Connection state change: " + this.pc.iceConnectionState); + this.onStateChange(this.mapStateToConnectionState()); + }; + this.stream.getAudioTracks().forEach((track) => this.pc!.addTrack(track)); + console.log("PC created"); + } + async getStatistics() { if (this.pc) { return await this.pc.getStats(); From 77436dea052af355b22f9eab62a3b36603a15e32 Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Fri, 24 Apr 2020 12:22:37 +0200 Subject: [PATCH 2/5] Tell shittyserver about TURN as well Not sure it needs it but... --- shittyserver.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/shittyserver.py b/shittyserver.py index 15834da..373c0dc 100644 --- a/shittyserver.py +++ b/shittyserver.py @@ -13,7 +13,7 @@ import aiohttp import av # type: ignore import jack as Jack # type: ignore import websockets -from aiortc import MediaStreamTrack, RTCPeerConnection, RTCSessionDescription # type: ignore +from aiortc import MediaStreamTrack, RTCPeerConnection, RTCConfiguration, RTCSessionDescription # type: ignore from aiortc.mediastreams import MediaStreamError # type: ignore from raygun4py import raygunprovider # type: ignore @@ -161,6 +161,7 @@ class Session(object): running: bool ended: bool resampler: Optional[Any] + ice_servers: Optional[TurnCredentials] def __init__(self) -> None: self.websocket = None @@ -173,6 +174,7 @@ class Session(object): self.lock = asyncio.Lock() self.running = False self.connected_at = datetime.now(timezone.utc) + self.ice_servers = None def to_dict(self) -> Dict[str, str]: return { @@ -239,7 +241,10 @@ class Session(object): self.ended = True def create_peerconnection(self) -> None: - self.pc = RTCPeerConnection() + assert self.ice_servers is not None + rtc_config = RTCConfiguration() + rtc_config.iceServers = self.ice_servers + self.pc = RTCPeerConnection(rtc_config) assert self.pc is not None @self.pc.on("signalingstatechange") # type: ignore @@ -346,11 +351,11 @@ class Session(object): self.websocket = websocket self.connection_state = "HELLO" print(self.connection_id, "Connected") - ice_config = get_turn_credentials() + self.ice_servers = get_turn_credentials() print(self.connection_id, "Obtained ICE") # TODO Raygun user ID await websocket.send( - json.dumps({"kind": "HELLO", "connectionId": self.connection_id, "iceServers": ice_config}) + json.dumps({"kind": "HELLO", "connectionId": self.connection_id, "iceServers": self.ice_servers}) ) try: From 723f1e3a2adb48d36025ae5898b925c5bef29aae Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Thu, 23 Apr 2020 22:14:32 +0200 Subject: [PATCH 3/5] Add Git hash to builds --- Jenkinsfile | 4 ++-- src/optionsMenu/AboutTab.tsx | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index f7e4a7a..14149ad 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -51,7 +51,7 @@ pipeline { } steps { sh 'sed -i -e \'s/ury.org.uk\\/webstudio/ury.org.uk\\/webstudio-dev/\' package.json' - sh 'yarn build' + sh 'REACT_APP_GIT_SHA=`git rev-parse --short HEAD` yarn build' sshagent(credentials: ['ury']) { sh 'rsync -av --delete-after build/ deploy@ury:/usr/local/www/webstudio-dev' } @@ -71,7 +71,7 @@ pipeline { } steps { sh 'sed -i -e \'s/ury.org.uk\\/webstudio-dev/ury.org.uk\\/webstudio/\' package.json' - sh 'yarn build' + sh 'REACT_APP_GIT_SHA=`git rev-parse --short HEAD` yarn build' sshagent(credentials: ['ury']) { sh 'rsync -av --delete-after build/ deploy@ury:/usr/local/www/webstudio' } diff --git a/src/optionsMenu/AboutTab.tsx b/src/optionsMenu/AboutTab.tsx index d6c5ef1..b2157bb 100644 --- a/src/optionsMenu/AboutTab.tsx +++ b/src/optionsMenu/AboutTab.tsx @@ -18,6 +18,9 @@ export function AboutTab() {
WebStudio v{process.env.REACT_APP_VERSION}
+
+ Git hash: {process.env.REACT_APP_GIT_SHA} +
MyRadio endpoint: {MYRADIO_BASE_URL}/ {MYRADIO_NON_API_BASE} From 25db9b10ee10e5577e3b535b2b9d549ca5961ab8 Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Fri, 24 Apr 2020 12:24:22 +0200 Subject: [PATCH 4/5] Bump v/n --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ed36018..5cb8efc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "webstudio", - "version": "1.0.0", + "version": "1.0.1", "private": true, "dependencies": { "@babel/core": "7.6.0", From 9676dfe1820d32fc53514f2e663ede68a741bf20 Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Fri, 24 Apr 2020 12:29:19 +0200 Subject: [PATCH 5/5] Revert "Tell shittyserver about TURN as well" This reverts commit 77436dea052af355b22f9eab62a3b36603a15e32. --- shittyserver.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/shittyserver.py b/shittyserver.py index 373c0dc..15834da 100644 --- a/shittyserver.py +++ b/shittyserver.py @@ -13,7 +13,7 @@ import aiohttp import av # type: ignore import jack as Jack # type: ignore import websockets -from aiortc import MediaStreamTrack, RTCPeerConnection, RTCConfiguration, RTCSessionDescription # type: ignore +from aiortc import MediaStreamTrack, RTCPeerConnection, RTCSessionDescription # type: ignore from aiortc.mediastreams import MediaStreamError # type: ignore from raygun4py import raygunprovider # type: ignore @@ -161,7 +161,6 @@ class Session(object): running: bool ended: bool resampler: Optional[Any] - ice_servers: Optional[TurnCredentials] def __init__(self) -> None: self.websocket = None @@ -174,7 +173,6 @@ class Session(object): self.lock = asyncio.Lock() self.running = False self.connected_at = datetime.now(timezone.utc) - self.ice_servers = None def to_dict(self) -> Dict[str, str]: return { @@ -241,10 +239,7 @@ class Session(object): self.ended = True def create_peerconnection(self) -> None: - assert self.ice_servers is not None - rtc_config = RTCConfiguration() - rtc_config.iceServers = self.ice_servers - self.pc = RTCPeerConnection(rtc_config) + self.pc = RTCPeerConnection() assert self.pc is not None @self.pc.on("signalingstatechange") # type: ignore @@ -351,11 +346,11 @@ class Session(object): self.websocket = websocket self.connection_state = "HELLO" print(self.connection_id, "Connected") - self.ice_servers = get_turn_credentials() + ice_config = get_turn_credentials() print(self.connection_id, "Obtained ICE") # TODO Raygun user ID await websocket.send( - json.dumps({"kind": "HELLO", "connectionId": self.connection_id, "iceServers": self.ice_servers}) + json.dumps({"kind": "HELLO", "connectionId": self.connection_id, "iceServers": ice_config}) ) try: